A friend of mine posed a seemingly innocuous Scala language question last week that I didn't have a good answer to: whether there's an easy way to declare a collection of things belonging to some common typeclass. Of course there's no first-class notion of "typeclass" in Scala, so we have to think of this in terms of traits and context bounds (i.e. implicits).
Concretely, given some trait T[_]
representing a typeclass, and types A
, B
and C
, with corresponding implicits in scope T[A]
, T[B]
and T[C]
, we want to declare something like a List[T[a] forAll { type a }]
, into which we can throw instances of A
, B
and C
with impunity. This of course doesn't exist in Scala; a question last year discusses this in more depth.
The natural follow-up question is "how does Haskell do it?" Well, GHC in particular has a type system extension called impredicative polymorphism, described in the "Boxy Types" paper. In brief, given a typeclass T
one can legally construct a list [forall a. T a => a]
. Given a declaration of this form, the compiler does some dictionary-passing magic that lets us retain the typeclass instances corresponding to the types of each value in the list at runtime.
Thing is, "dictionary-passing magic" sounds a lot like "vtables." In an object-oriented language like Scala, subtyping is a much more simple, natural mechanism than the "Boxy Types" approach. If our A
, B
and C
all extend trait T
, then we can simply declare List[T]
and be happy. Likewise, as Miles notes in a comment below, if they all extend traits T1
, T2
and T3
then I can use List[T1 with T2 with T3]
as an equivalent to the impredicative Haskell [forall a. (T1 a, T2 a, T3 a) => a]
.
However, the main, well-known disadvantage with subtyping compared to typeclasses is tight coupling: my A
, B
and C
types have to have their T
behavior baked in. Let's assume this is a major dealbreaker, and I can't use subtyping. So the middle ground in Scala is pimps^H^H^H^H^Himplicit conversions: given some A => T
, B => T
and C => T
in implicit scope, I can again quite happily populate a List[T]
with my A
, B
and C
values...
... Until we want List[T1 with T2 with T3]
. At that point, even if we have implicit conversions A => T1
, A => T2
and A => T3
, we can't put an A
into the list. We could restructure our implicit conversions to literally provide A => T1 with T2 with T3
, but I've never seen anybody do that before, and it seems like yet another form of tight coupling.
Okay, so my question finally is, I suppose, a combination of a couple questions that were previously asked here: "why avoid subtyping?" and "advantages of subtyping over typeclasses" ... is there some unifying theory that says impredicative polymorphism and subtype polymorphism are one and the same? Are implicit conversions somehow the secret love-child of the two? And can somebody articulate a good, clean pattern for expressing multiple bounds (as in the last example above) in Scala?
List[T]
is an adequate (in context) translation for list[forall a. T a => a]
, then why isn'tList[T1 with T2 with T3]
an adequate translation for list[forall a. (T1 a, T2 a, T3 a) => a]
? – Miles SabinT1
,T2
andT3
if I can use implicit conversions instead. Also thank you for the opportunity to use the phrase "secret love-child" in a question about type systems on StackOverflow :) – mergeconflict[forall a. Show a => a]
means a list of all types a such thatShow a
;[()]
does not satisfy this type, since()
is of the more specific type()
. I'm not sure there are any values that satisfy this type, but it's not what you wanted anyway; to get what you wanted, you need an existential qualification which requires wrapping a new data constructor around it (although it doesn't have to be a GADT). – mithrandi