4 ms·
In popular OOP languages (C#, Java, etc), interfaces give you a way to state that some existentially quantified type implements some set of common methods. Here
by cstrahan 9y ago
In popular OOP languages (C#, Java, etc), interfaces give you a way to state that some existentially quantified type implements some set of common methods. Here's what I mean by "existentially quantified":
Let's say you have some function called GetStringableThing() that returns returns, as its name suggests, some object that implements the Stringable interface -- where Stringable is just an interface that consists of a ToString() method. As the caller of GetStringableThing(), you have no say in precisely which type of Stringable thing you'll get -- that's up to the callee. So there exists some type that implements Stringable, which GetStringableThing() would be more than happy to give you. This is in contrast with universal quantification; if the return type of GetStringableThing() was universally quantified, that would mean that you as the caller get to dictate the return type (so long as it implements Stringable), and its the callee's responsibility to be able to return any type the caller demands.
Typeclasses (as in Haskell) have the benefit of allowing you, as the caller of a function, to dictate the return type of that function -- in fact, universal quantification is the default, and you have to go out of your way (e.g. with GADTs) to represent existential types.
This now leads us to a common pitfall for people coming to Haskell from OOP languages, where their intuition that "typeclass == interface" fails them:
-- The intent here is the same as earlier with "Stringable",
-- though in Haskell that would be "Show".
--
-- A beginner's intuition:
-- "Surely this reads 'somethingShowable is some type implementing the Show interface', right?"
somethingShowable :: (Show a) => a
somethingShowable = (42 :: Int)
A beginner will often think that the above should compile ("42 is an Int, and Int has a Show instance, so this should work, right? I just need to provide something that has a Show instance, right?"). Unfortunately, it doesn't compile. The above code is making the promise that it can provide a value of any type that the caller demands (so long as it implements Show), but here we're only providing an integer specifically.
This bit of OOP pseudo-code captures what they we're going for:
func getStringableThing() Stringable {
return 42
}
A natural implication of this universal vs existential quantification distinction is this:
Typeclass instances are resolved at compile time (except where you've gone out of your way to existentially type something), and can be specialized and inlined accordingly. Interfaces (a la OOP) are implemented via some sort of vtable mechanism, where dispatch is deferred until runtime (though a JIT can potentially do some inlining).
Interfaces (a la OOP) and typeclasses are implemented (and behave) quite differently, so I would argue that if Haskell were to replace its use of "class" (and "typeclass") with "interface" it would only confuse things further (as evidenced by the constant stream of beginners asking for help where they've made this exact conflation; for the record, I was one of these beginners 3 years ago).
The "they shouldn't call these (type)classes!" complaint is quite tiring: no one ever has a reasonable alternative to offer. At least (type)class makes some sense when you realize that "class" is meant in the mathematical sense of the word "class".