I'm trying to enforce a type-level constraint that a type-level list must be the same length as a type-level Nat being carried around. For example, using Length from singletons [1] package:
data (n ~ Length ls) => NumList (n :: Nat) (ls :: [*])
test :: Proxy (NumList 2 '[Bool, String, Int])
test = Proxy
I would not expect this code to compile, since there is a mismatch.
EDIT: As dfeuer mentioned Datatype contexts aren't a good idea. I can do the comparison at the value level, but I want to be able to do this at the type level:
class NumListLen a
sameLen :: Proxy a -> Bool
instance (KnownNat n, KnownNat (Length m)) => NumListLen (NumList n m) where
sameLen = const $ (natVal (Proxy :: Proxy n)) == (natVal (Proxy :: Proxy (Length m)))
~~~~
EDIT: Sorta answered my own question, simply add the constraint to the instance:
class NumListLen a
sameLen :: Proxy a -> Bool
instance (KnownNat n, KnownNat (Length m), n ~ Length m) => NumListLen (NumList n m) where
sameLen = const $ (natVal (Proxy :: Proxy n)) == (natVal (Proxy :: Proxy (Length m)))
/home/aistis/Projects/SingTest/SingTest/app/Main.hs:333:13:
Couldn't match type ‘3’ with ‘2’
In the second argument of ‘($)’, namely ‘sameLen test’
In a stmt of a 'do' block: print $ sameLen test
In the expression:
do { print $ sameLen test;
putStrLn "done!" }
If this is something like an invariant (which it seems it is), you should store the proof in the datatype:
{-# LANGUAGE PolyKinds, UndecidableInstances #-}
import GHC.TypeLits
type family Length (xs :: [k]) :: Nat where
Length '[] = 0
Length (x ': xs) = 1 + Length xs
data TList n l where
TList :: (Length xs ~ n) => TList n xs
Note that while the proof is still available at the type level, it is sort of "hidden" behind the data constructor. You can recover the proof simply by pattern matching:
data (:~:) a b where Refl :: a :~: a
test :: TList n l -> Length l :~: n
test TList = Refl
Now, mismatches between the two parameters are a type error:
bad :: TList 3 '[Int, Bool]
bad = TList
good :: TList 2 '[Int, Bool]
good = TList
Of course this can still be beaten by bottom values, so
uh_oh :: TList 10 '[]
uh_oh = undefined
To avoid this, simply make sure you always pattern match on the TList
constructor.
One option might be to use a type family:
data Nat = Z | S Nat
type family LengthIs (n :: Nat) (xs :: [*]) :: Bool where
LengthIs 'Z '[] = 'True
LengthIs ('S n) (x ': xs) = LengthIs n xs
LengthIs n xs = 'False
test :: LengthIs ('S ('S 'Z)) '[Bool,String,Int] ~ 'True => ()
test = ()
This will not pass the type checker; the only way to make it pass is to make the type list have two elements. I don't know how Nat
works in the singletons library, but I imagine you might be able to do something similar.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.