Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type family evaluation in dependent kind signature

Consider the following:

type Nat :: Type
data Nat = Z | S Nat

type Fin :: Nat -> Type
data Fin n
  where
  FZ :: Fin ('S n)
  FS :: Fin n -> Fin ('S n)

type Length :: [k] -> Nat
type family Length xs
  where
  Length '[] = 'Z
  Length (_ ': xs) = Length xs

Given all this, it seems like it should be possible to define a type level function that maps a type level list and an index smaller than its length to the type stored at the corresponding position.

type Get :: forall k. forall (l :: [k]) -> Fin (Length l) -> k
type family Get l i
  where
  Get (x ': xs) 'FZ = x
  Get (_ ': xs) ('FS i) = Get xs i

But this blows up with the following error:

• Illegal type synonym family application ‘Length @k xs’ in instance:
    Get @k ((':) @k x xs) ('FZ @(Length @k xs))
• In the equations for closed type family ‘Get’
  In the type family declaration for ‘Get’

So it seems GHC is not happy with the evaluation of the Length type family in the dependent kind signature. Is there some way to convince GHC that it's OK to define such a function? Alternatively, is there some workaround I can use to describe the dependently-kinded operation of indexing into a type level list?

like image 957
Asad Saeeduddin Avatar asked Sep 11 '21 10:09

Asad Saeeduddin


1 Answers

I'm personally not aware of a way to make this work (hoping someone else is). That said, we can work around the need to evaluate a type family in the kind signature if we absorb Length into the definition of Fin. In other words, we can create a special purpose indexing type for lists that mirrors Fin (Length l):

type Index :: [k] -> Type
data Index xs
  where
  Head :: Index (x ': xs)
  Next :: Index xs -> Index (x ': xs)

Now we can use that to write a variation of Get:

type Get :: forall k. forall (l :: [k]) -> Index l -> k
type family Get l i
  where
  Get (x ': xs) 'Head = x
  Get (_ ': xs) ('Next i) = Get xs i

Which seems to work:

type Test = Get '[Int, String, Bool] (Next (Next Head))
> :k! Test
Test :: *
= Bool

This approach is kind of gross and anti-compositional, but I suppose it works.

like image 96
Asad Saeeduddin Avatar answered Nov 09 '22 22:11

Asad Saeeduddin