类型级别列表上的平等约束

时间:2016-01-13 03:12:11

标签: haskell dependent-type type-level-computation

我正在尝试强制执行类型级别约束,类型级别列表的长度必须与携带的类型级别Nat的长度相同。例如,使用单身长度[1]包:

data (n ~ Length ls) => NumList (n :: Nat) (ls :: [*])

test :: Proxy (NumList 2 '[Bool, String, Int])
test = Proxy

我不希望编译这个代码,因为存在不匹配。

编辑:正如dfeuer提到的数据类型上下文不是一个好主意。我可以在值级别进行比较,但我希望能够在类型级别执行此操作:

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)))

~~~~

编辑:Sorta回答了我自己的问题,只需将约束添加到实例:

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!" }

[1] https://hackage.haskell.org/package/singletons-2.0.0.2/docs/Data-Promotion-Prelude-List.html#t:Length

2 个答案:

答案 0 :(得分:4)

如果这类似于一个不变量(它似乎是这样),你应该将证据存储在数据类型中:

{-# 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 

请注意,虽然证明在类型级别仍然可用,但它在数据构造函数后面是“隐藏”的。您只需通过模式匹配即可恢复证明:

data (:~:) a b where Refl :: a :~: a 

test :: TList n l -> Length l :~: n 
test TList = Refl 

现在,两个参数之间的不匹配是一个类型错误:

bad :: TList 3 '[Int, Bool]
bad = TList 

good :: TList 2 '[Int, Bool]
good = TList 

当然,这仍然会受到底价值的影响,所以

uh_oh :: TList 10 '[] 
uh_oh = undefined 

要避免这种情况,只需确保始终在TList构造函数上进行模式匹配。

答案 1 :(得分:3)

一种选择可能是使用类型系列:

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 = ()

这不会通过类型检查器;使其通过的唯一方法是使类型列表具有两个元素。我不知道Nat在单身人士图书馆中是如何运作的,但我想你可能会做类似的事情。