I'm reading up about the Lambda-Cube, and I'm particularly interested in System F-omega, which allows for "type operators" i.e. types depending on types. This sounds a lot like GHC's type families. For example
type family Foo a
type instance Foo Int = Int
type instance Foo Float = ...
...
where the actual type depends on the type parameter a
. Am I right in thinking that type families are an example of the type operators ala system F-omega? Or am I out in left field?
System F-omega allows universal quantification, abstraction and application at higher kinds, so not only over types (at kind
*
), but also at kindsk1 -> k2
, wherek1
andk2
are themselves kinds generated from*
and->
. Hence, the type level itself becomes a simply typed lambda-calculus.Haskell delivers slightly less than F-omega, in that the type system allows quantification and application at higher kinds, but not abstraction. Quantification at higher kinds is how we have types like
with
f :: * -> *
. Correspondingly, variables likef
can be instantiated with higher-kinded type expressions, such asEither String
. The lack of abstraction makes it possible to solve unification problems in type expressions by the standard first-order techniques which underpin the Hindley-Milner type system.However, type families are not really another means to introduce higher-kinded types, nor a replacement for the missing type-level lambda. Crucially, they must be fully applied. So your example,
should not be considered as introducing some
because
Foo
on its own is not a meaningful type expression. We have only the weaker rule thatFoo t :: *
whenevert :: *
.Type families do, however, act as a distinct type-level computation mechanism beyond F-omega, in that they introduce equations between type expressions. The extension of System F with equations is what gives us the "System Fc" which GHC uses today. Equations
s ~ t
between type expressions of kind*
induce coercions transporting values froms
tot
. Computation is done by deducing equations from the rules you give when you define type families.Moreover, you can give type families a higher-kinded return type, as in
so that
Hoo t :: * -> *
whenevert :: *
, but still we cannot letHoo
stand alone.The trick we sometimes use to get around this restriction is
newtype
wrapping:which does indeed give us
but means that we have to apply the projection to make computation happen, so
Noo Int
andInt
are provably distinct types, butSo it's a bit clunky, but we can kind of compensate for the fact that type families do not directly correspond to type operators in the F-omega sense.