I am trying to do some advanced type-level programming; the example is a reduced version of my original program.
I have a representation of (Haskell) types. In this example I only cover function types, basic types and type variables.
The representation Type t
is parameterized by one type variable t
to also allow a differentiation on the type-level. To achieve this, I am mainly using GADTs. Different types and type variables are differentiated by using type-level literals, thus the KnownSymbol
constraint and the use of Proxy
s.
{-# LANGUAGE GADTs, TypeOperators, DataKinds, KindSignatures, TypeFamilies, PolyKinds #-}
import GHC.TypeLits
import Data.Proxy
import Data.Type.Equality
data Type :: TypeKind -> * where
TypeFun :: Type a -> Type b -> Type (a :-> b)
Type :: KnownSymbol t => Proxy t -> Type (Ty t)
TypeVar :: KnownSymbol t => Proxy t -> Type (TyVar t)
I have also restricted the kind of t
to be of kind TypeKind
by using the DataKinds and KindSignatures extensions and defining the TypeKind
data-type:
data TypeKind =
Ty Symbol
| TyVar Symbol
| (:->) TypeKind TypeKind
Now I want to implement substitutions of type variables, i.e. substituting, within a type t
, every variable x
that is equal to a type variable y
, with type t'
. The substitution must be implemented on the representation, as well as on the type-level. For the latter, we need TypeFamilies:
type family Subst (t :: TypeKind) (y :: Symbol) (t' :: TypeKind) :: TypeKind where
Subst (Ty t) y t' = Ty t
Subst (a :-> b) y t' = Subst a y t' :-> Subst b y t'
Subst (TyVar x) y t' = IfThenElse (x == y) t' (TyVar x)
The type variables are the interesting part, because we check for the equality of the symbols x
and y
on the type-level. For this, we also need a (poly-kinded) type family that allows us to choose between two results:
type family IfThenElse (b :: Bool) (x :: k) (y :: k) :: k where
IfThenElse True x y = x
IfThenElse False x y = y
Unfortunately, this does not compile yet, which might be the first indicator of my problem:
Nested type family application
in the type family application: IfThenElse (x == y) t' ('TyVar x)
(Use UndecidableInstances to permit this)
In the equations for closed type family ‘Subst’
In the type family declaration for ‘Subst’
Enabling the UndecidableInstances extension works, though, so we continue to define a function subst
that works on the value-level:
subst :: (KnownSymbol y) => Type t -> Proxy (y :: Symbol) -> Type t' -> Type (Subst t y t')
subst (TypeFun a b) y t = TypeFun (subst a y t) (subst b y t)
subst t@(Type _) _ _ = t
subst t@(TypeVar x) y t'
| Just Refl <- sameSymbol x y = t'
| otherwise = t
This code works perfectly, except for the last line which produces the following compilation error:
Could not deduce (IfThenElse
(GHC.TypeLits.EqSymbol t1 y) t' ('TyVar t1)
~ 'TyVar t1)
from the context (t ~ 'TyVar t1, KnownSymbol t1)
bound by a pattern with constructor
TypeVar :: forall (t :: Symbol).
KnownSymbol t =>
Proxy t -> Type ('TyVar t),
in an equation for ‘subst’
at Type.hs:29:10-18
Expected type: Type (Subst t y t')
Actual type: Type t
Relevant bindings include
t' :: Type t' (bound at Type.hs:29:23)
y :: Proxy y (bound at Type.hs:29:21)
x :: Proxy t1 (bound at Type.hs:29:18)
subst :: Type t -> Proxy y -> Type t' -> Type (Subst t y t')
(bound at Type.hs:27:1)
In the expression: t
In an equation for ‘subst’:
subst t@(TypeVar x) y t'
| Just Refl <- sameSymbol x y = t'
| otherwise = t
I guess that the problem is that I cannot prove the inequality of the types of the two symbols x
and y
, and would need some kind of type-inequality witness. Is this possible? Or is there another, better way to achieve my goal?
I do not know to which extent the questions 'idiomatic' Haskell type inequality and Can GADTs be used to prove type inequalities in GHC? already answer my question. Any help would be appreciated.