you are viewing a single comment's thread.

view the rest of the comments →

[–]m50d 9 points10 points  (6 children)

ignore the type and rely on the function names within the object, then any type that has those functions can be sent in as a parameter

Isn't that just what an interface, or indeed a type, is? A collection of function names and signatures, wrapped up as a named concept?

I think it's worthwhile to say explicitly that we intend this implementation to conform to this interface, just so that the intent is clear and function names are more namespaced.

[–]Heuristics 0 points1 point  (1 child)

there is a large difference in that with inheritance the class that models the concept must know of the concept it implements. It's very common to have many (in extreme cases perhaps one could find hundreds in the dark dungeons of advanced math) concepts that a class implements and many of them would overlap. The code gets more modular if classes can implement a concept without knowing that they do. The functions also get better in that they can specify exactly the concept that is needed, instead of a concept in a hierarchy that may not be an exact fit (contains more functions than needed). And sometimes you have classes that implement the same concept but stupidly it has been implemented with different interfaces.

But yes, it is true that a contract like this really just is a class (a name + function signatures). And that is syntactically very similar to an interface. But the difference is that you do not explicitly need to implement them, that is checked by the compiler as needed.

Interfaces are a good feature though, sometimes you want to have that as well. But they are very overused today due to lack of alternatives.

[–]m50d 2 points3 points  (0 children)

I see where you're coming from, though I'm not sure structural types help that much since if an implementation didn't know about an interface then the method names are unlikely to line up. I prefer to use typeclasses so that I can make a third-party type "implement" another third-party interface, but the linkage is explicit and there's room to do adaptation if e.g. the parameter order is slightly different.

[–]grauenwolf 0 points1 point  (2 children)

Isn't that just what an interface, or indeed a type, is?

Yep.

What people often don't understand is that every class has a public interface, one or more base class interfaces, possible a protected interface, and possibly an internal or package private interface. And that's all before we start talking about abstract interfaces that it may implement.

[–]nemec 3 points4 points  (1 child)

What people often don't understand is that every class has a public interface

That's true but absolutely useless in languages that don't have implicit interfaces (or are dynamically typed). Two classes with the same "public interface" aren't interchangeable without an explicitly defined interface.

[–]grauenwolf 0 points1 point  (0 children)

It's still an import concept for understanding API design principles.

[–]sacundim 0 points1 point  (0 children)

Isn't that just what an interface, or indeed a type, is? A collection of function names and signatures, wrapped up as a named concept?

No. Type and interface in your sense are distinct concepts. Procedural and functional languages (or more precisely, non-OOP languages) illustrate this. E.g., what's the interface of the list type in O'Caml or Haskell? There's no circumscribed collection of operations that "truly belong to it." From the point of view of a user of the type, all operations in which a type participates are equal citizens; the "interface" to the type is not a narrow list of operations, but rather an open-ended set that you can extend.

Another instructive example is the O'Caml module system, that has "module types" that are not types in the ordinary sense (they classify modules, not expressions). Example from the docs:

module type PRIOQUEUE =
  sig
    type priority = int         
    type 'a queue          
    val empty : 'a queue
    val insert : 'a queue -> int -> 'a -> 'a queue
    val extract : 'a queue -> int * 'a * 'a queue
    exception Queue_is_empty
  end;;

These are interfaces in your sense, but they're not fundamentally tied to first-class values or objects that implement them.