User login

Navigation

GADT vs. Type Classes

I'm looking for a paper or tutorial which compares/contrasts generalized algebraic data types and type classes (as found in GHC/Haskell). I get the feeling that they are some how duals of each other, and that there may be some overlap of the capabilities of each, but I don't know if I understand the essential differences which makes both desirable. For example, the standard GADT example of a tagless interpreter looks pretty similar to an equivalent built using type classes. In fact, the code for "eval" is identical, only the type annotations differ. Any thoughts, comments?

Comment viewing options

And if there are any GADT experts out there who are looking for a challenge, you might try to use GADTs in the following data structure to eliminate the existential types (which make writing functions like "tail" problematic). The "Fancy" data structure is statically constrained to contain linearly increasing runs of elements of alternating types. So for example, you can start out with one Int and then two Strings, then three Ints, four Strings, etc.

I am not competent enough to really characterize the difference, but I guess one important difference in your example is that the type class solution requires "deep" types, i.e., the structure of the terms is fully reified at the type level. This is not the case in the GADT solution.

If you consider, say, a parsing function, then I would imagine that you can only give a reasonable implementation using the GADT approach since the structure (and hence type) of the expressions cannot be predicted by the type system in the type class approach.

This answer is really a bit late, but I just stumbled across your post by chance. My paper "polymorphic typed defunctionalization and concretization" (with Nadji Gauthier) shows a type-preserving encoding of type classes into GADTs. This proves, in a way, that "anything that can be done with type classes can be done with GADTs". However, for the encoding to be used in practice, you need either a closed world assumption or a form of extensible GADTs.

This is even later, but I thought people might find it interesting to see how this example looks in Java. We can translate the GADT into a set of classes implementing a parameterized interfaces in a quite direct and type-safe way:

There is a very clear correspondence between notions of GADTs in a functional setting (algebraic types whose constructors introduce existential types and use type equality constraints) and a counter-part in an object-oriented setting (classes whose methods universally quantify over types, and use type equality constraints), but those types and classes are not at all mapped one-to-one by the usual "sums into distinct classes, functions into methods" correspondence between functional and object-object languages.

That is, you can take a classic functional GADT example (well-typed expressions with an Eval function) and its object-oriented transcription will require no type equality constraints. Conversely, a completely trivial example (List.flatten, or Coproduct.flatten) will suddenly be impossible to express in an OO language without type equality (or subtyping) constraints.

This situation has resulted in people claiming that, for example, "Scala has GADTs" on the incorrect basis of managing to translate one paradigmatic functional GADT example without needing type equalities. If you look at the details, it turns out that Scala can in fact express GADTs through rather ugly workarounds (you can quantify on (forall x such that foo <= x <= bar), which adds a foo <= bar constraint, and therefore by abstracting over two dummy variables add an equality constraints between existing types), or abstract over an implicit runtime equality/coercion witness (which is what the Scala standard library does for List.flatten). You're out of luck with Java.

(Finally, in OO languages that are subtyping-heavy it is more natural to add subtyping constraints rather than type equality constraints. There is a further paper by Burak Emir, Andrew Kennedy, Claudio Russo and Dachuan Yu, "Variance and Generalized Constraints for C# Generics" that details this construction.)

I think the confusion about the existence of GADTs in various languages
comes because of a deeply unfortunate example of a typed
interpreter. Almost any paper on GADT (except ours at the ML 2010
workshop) uses this flawed example. But it does not require GADT at
all, if we use the tagless-final encoding, explained in detail inhttp://okmij.org/ftp/tagless-final/course/course.html#lecture

That presentation also points out the litmus test for GADT (which
first-class modules fail, btw). One can say that their language
supports GADTs if they implement the Equality GADT and it
supports injectivity. The injectivity, that is, deriving EQ a
b from EQ [a] [b] is very difficult to fake.

"Term/type level type" is essential I think. If you have some dependencies on the term-level represented by some function you may lift this dependencies in the type level and thus check some properties at compile time and vice versa if you don't need some dependencies represented by type classes to be checked at compile time you may consider GADT's.

I'm not sure what you're commenting on (I don't see the link with the post above, but I won't watch the video anyway, so I'll just assume that it's similar to "Data Types Ã la Carte"), but I'm tempted to disagree in any case.

In Data Types Ã la Carte a value has type Foo (A :+: B :+: C) where A, B, C are the constructors that are allowed to appear in the value. In the presentation above, a value has a type that mirrors its structure. So Add (Const 1) (Add (Const 2) (Const 3)) has type Add Const (Add Const Const). You can introduce an existential but then you have to decide at that point which operations you want to support, so then in my opinion that does not solve the expression problem because it lacks extensibility of operations. Perhaps I missed a key point because I went through the video quickly, in any case in the slides there is no solution to this problem.