将我的脚趾浸入依赖类型的水域中,我对规范的“静态类型长度列表”示例进行了修改。
{-# LANGUAGE DataKinds, GADTs, KindSignatures #-}
-- a kind declaration
data Nat = Z | S Nat
data SafeList :: (Nat -> * -> *) where
Nil :: SafeList Z a
Cons :: a -> SafeList n a -> SafeList (S n) a
-- the type signature ensures that the input list has at least one element
safeHead :: SafeList (S n) a -> a
safeHead (Cons x xs) = x
这似乎有效:
ghci> :t Cons 5 (Cons 3 Nil)
Cons 5 (Cons 3 Nil) :: Num a => SafeList ('S ('S 'Z)) a
ghci> safeHead (Cons 'x' (Cons 'c' Nil))
'x'
ghci> safeHead Nil
Couldn't match type 'Z with 'S n0
Expected type: SafeList ('S n0) a0
Actual type: SafeList 'Z a0
In the first argument of `safeHead', namely `Nil'
In the expression: safeHead Nil
In an equation for `it': it = safeHead Nil
但是,为了使这个数据类型真正有用,我应该能够从运行时数据构建它,在编译时你不知道它的长度。我天真的尝试:
fromList :: [a] -> SafeList n a
fromList = foldr Cons Nil
无法编译,类型错误:
Couldn't match type 'Z with 'S n
Expected type: a -> SafeList n a -> SafeList n a
Actual type: a -> SafeList n a -> SafeList ('S n) a
In the first argument of `foldr', namely `Cons'
In the expression: foldr Cons Nil
In an equation for `fromList': fromList = foldr Cons Nil
我理解为什么会发生这种情况:Cons
的返回类型对于折叠的每次迭代都是不同的 - 这就是重点!但我无法看到解决方法,可能是因为我没有深入阅读这个主题。 (我无法想象所有这些努力都被放入一个在实践中无法使用的类型系统!)
那么:如何从“普通”简单类型数据中构建这种依赖类型的数据?
按照@ luqui的建议,我能够fromList
编译:
data ASafeList a where
ASafeList :: SafeList n a -> ASafeList a
fromList :: [a] -> ASafeList a
fromList = foldr f (ASafeList Nil)
where f x (ASafeList xs) = ASafeList (Cons x xs)
这是我尝试解压缩ASafeList
并使用它:
getSafeHead :: [a] -> a
getSafeHead xs = case fromList xs of ASafeList ys -> safeHead ys
这会导致另一种类型错误:
Couldn't match type `n' with 'S n0
`n' is a rigid type variable bound by
a pattern with constructor
ASafeList :: forall a (n :: Nat). SafeList n a -> ASafeList a,
in a case alternative
at SafeList.hs:33:22
Expected type: SafeList ('S n0) a
Actual type: SafeList n a
In the first argument of `safeHead', namely `ys'
In the expression: safeHead ys
In a case alternative: ASafeList ys -> safeHead ys
同样,直观地认为这将无法编译。我可以使用空列表调用fromList
,因此编译器无法保证我能够在生成的safeHead
上调用SafeList
。缺乏知识大致是存在主义ASafeList
捕获的。
这个问题可以解决吗?我觉得自己可能走在了一个合乎逻辑的死胡同。
答案 0 :(得分:16)
永远不要扔掉任何东西。
如果你要麻烦地沿着一个列表来制作长度索引列表(在文献中称为“向量”),你也可以记住它的长度。
所以,我们有
data Nat = Z | S Nat
data Vec :: Nat -> * -> * where -- old habits die hard
VNil :: Vec Z a
VCons :: a -> Vec n a -> Vec (S n) a
但我们也可以给静态长度一个运行时表示。 Richard Eisenberg的“Singletons”软件包将为您完成此任务,但基本思路是为静态数字提供一种运行时表示。
data Natty :: Nat -> * where
Zy :: Natty Z
Sy :: Natty n -> Natty (S n)
至关重要的是,如果我们有Natty n
类型的值,那么我们可以查询该值以找出n
是什么。
Hasochists知道运行时可表示性通常很无聊,即使是机器也可以管理它,所以我们将它隐藏在类型类中
class NATTY (n :: Nat) where
natty :: Natty n
instance NATTY Z where
natty = Zy
instance NATTY n => NATTY (S n) where
natty = Sy natty
现在,我们可以对您从列表中获得的长度进行更具信息性的存在性处理。
data LenList :: * -> * where
LenList :: NATTY n => Vec n a -> LenList a
lenList :: [a] -> LenList a
lenList [] = LenList VNil
lenList (x : xs) = case lenList xs of LenList ys -> LenList (VCons x ys)
您获得的代码与长度破坏版本相同,但您可以随时获取长度的运行时表示,并且您无需沿着向量爬行即可获取它。
当然,如果您希望长度为Nat
,那么对某些Natty n
而言,n
仍然是一种痛苦。
将一个人的口袋弄得乱七八糟。
编辑我想我会添加一点,以解决“安全头”使用问题。
首先,让我为LenList
添加一个解包器,它会为您提供手中的号码。
unLenList :: LenList a -> (forall n. Natty n -> Vec n a -> t) -> t
unLenList (LenList xs) k = k natty xs
现在假设我定义了
vhead :: Vec (S n) a -> a
vhead (VCons a _) = a
强制执行安全财产。如果我有一个向量长度的运行时表示,我可以查看它是否适用vhead
。
headOrBust :: LenList a -> Maybe a
headOrBust lla = unLenList lla $ \ n xs -> case n of
Zy -> Nothing
Sy _ -> Just (vhead xs)
所以你要看一件事,这样做,了解另一件事。
答案 1 :(得分:5)
在
fromList :: [a] -> SafeList n a
n
普遍量化 - 即此签名声称我们应该能够从列表中构建任意长度的SafeList
。相反,您希望量化存在,这只能通过定义新的数据类型来完成:
data ASafeList a where
ASafeList :: SafeList n a -> ASafeList a
然后你的签名应该是
fromList :: [a] -> ASafeList a
您可以通过ASafeList
useList :: ASafeList a -> ...
useList (ASafeList xs) = ...
并且在正文中,xs
将是SafeList n a
类型,其中包含未知(刚性)n
。您可能需要添加更多操作才能以任何不平凡的方式使用它。
答案 2 :(得分:1)
如果要在运行时数据上使用依赖类型的函数,则需要确保此数据不违反类型签名法中的编码。通过一个例子更容易理解这一点。这是我们的设置:
data Nat = Z | S Nat
data Natty (n :: Nat) where
Zy :: Natty Z
Sy :: Natty n -> Natty (S n)
data Vec :: * -> Nat -> * where
VNil :: Vec a Z
VCons :: a -> Vec a n -> Vec a (S n)
我们可以在Vec
上编写一些简单的函数:
vhead :: Vec a (S n) -> a
vhead (VCons x xs) = x
vtoList :: Vec a n -> [a]
vtoList VNil = []
vtoList (VCons x xs) = x : vtoList xs
vlength :: Vec a n -> Natty n
vlength VNil = Zy
vlength (VCons x xs) = Sy (vlength xs)
为了编写lookup
函数的规范示例,我们需要finite sets的概念。它们通常定义为
data Fin :: Nat -> where
FZ :: Fin (S n)
FS :: Fin n -> Fin (S n)
Fin n
代表所有小于n
的数字。
但就像类型级别相当于Nat
s - Natty
s一样,类型级别相当于Fin
s。但现在我们可以合并价值级别和类型级别Fin
s:
data Finny :: Nat -> Nat -> * where
FZ :: Finny (S n) Z
FS :: Finny n m -> Finny (S n) (S m)
第一个Nat
是Finny
的上限。第二个Nat
对应于Finny
的实际值。即它必须等于toNatFinny i
,其中
toNatFinny :: Finny n m -> Nat
toNatFinny FZ = Z
toNatFinny (FS i) = S (toNatFinny i)
现在可以直接定义lookup
函数:
vlookup :: Finny n m -> Vec a n -> a
vlookup FZ (VCons x xs) = x
vlookup (FS i) (VCons x xs) = vlookup i xs
还有一些测试:
print $ vlookup FZ (VCons 1 (VCons 2 (VCons 3 VNil))) -- 1
print $ vlookup (FS FZ) (VCons 1 (VCons 2 (VCons 3 VNil))) -- 2
print $ vlookup (FS (FS (FS FZ))) (VCons 1 (VCons 2 (VCons 3 VNil))) -- compile-time error
这很简单,但take
函数怎么样?这并不难:
type Finny0 n = Finny (S n)
vtake :: Finny0 n m -> Vec a n -> Vec a m
vtake FZ _ = VNil
vtake (FS i) (VCons x xs) = VCons x (vtake i xs)
我们需要Finny0
而不是Finny
,因为lookup
要求Vec
非空,所以如果类型为Finny n m
的值,然后n = S n'
用于某些n'
。但是vtake FZ VNil
完全有效,所以我们需要放宽这个限制。因此Finny0 n
表示所有数字都小于或等于n
。
但是运行时数据呢?
vfromList :: [a] -> (forall n. Vec a n -> b) -> b
vfromList [] f = f VNil
vfromList (x:xs) f = vfromList xs (f . VCons x)
即。 "给我一个列表和一个函数,它接受任意长度的Vec
,并且我将后者应用于前者"。 vfromList xs
返回一个继续(即(a -> r) -> r
类型的东西)模数更高级别的类型。我们来试试吧:
vmhead :: Vec a n -> Maybe a
vmhead VNil = Nothing
vmhead (VCons x xs) = Just x
main = do
print $ vfromList ([] :: [Int]) vmhead -- Nothing
print $ vfromList [1..5] vmhead -- Just 1
作品。但我们不能重复自己吗?为什么vmhead
,当vhead
已经存在?我们是否应该以不安全的方式重写所有安全函数,以便可以在运行时数据上使用它们?那太傻了。
我们所需要的只是确保所有不变量都成立。让我们在vtake
函数上尝试这个原则:
fromIntFinny :: Int -> (forall n m. Finny n m -> b) -> b
fromIntFinny 0 f = f FZ
fromIntFinny n f = fromIntFinny (n - 1) (f . FS)
main = do
xs <- readLn :: IO [Int]
i <- read <$> getLine
putStrLn $
fromIntFinny i $ \i' ->
vfromList xs $ \xs' ->
undefined -- what's here?
fromIntFinny
就像vfromList
一样。看看有哪些类型是有益的:
i' :: Finny n m
xs' :: Vec a p
但是vtake
有这种类型:Finny0 n m -> Vec a n -> Vec a m
。因此,我们需要强制i'
,以使其类型为Finny0 p m
。并且toNatFinny i'
必须等于toNatFinny coerced_i'
。但是这种强制一般是不可能的,因为如果S p < n
,则Finny n m
中的元素不在Finny (S p) m
中,因为S p
和n
是上限。
coerceFinnyBy :: Finny n m -> Natty p -> Maybe (Finny0 p m)
coerceFinnyBy FZ p = Just FZ
coerceFinnyBy (FS i) (Sy p) = fmap FS $ i `coerceFinnyBy` p
coerceFinnyBy _ _ = Nothing
这就是Maybe
这里的原因。
main = do
xs <- readLn :: IO [Int]
i <- read <$> getLine
putStrLn $
fromIntFinny i $ \i' ->
vfromList xs $ \xs' ->
case i' `coerceFinnyBy` vlength xs' of
Nothing -> "What should I do with this input?"
Just i'' -> show $ vtoList $ vtake i'' xs'
在Nothing
情况下,从输入中读取的数字大于列表的长度。在Just
情况下,数字小于或等于列表的长度并强制转换为适当的类型,因此vtake i'' xs'
的类型很好。
这很有效,但是我们引入了coerceFinnyBy
函数,它看起来很特别。可判定的&#34;更少或相等&#34;关系将是适当的选择:
data (:<=) :: Nat -> Nat -> * where
Z_le_Z :: Z :<= m -- forall n, 0 <= n
S_le_S :: n :<= m -> S n :<= S m -- forall n m, n <= m -> S n <= S m
type n :< m = S n :<= m
(<=?) :: Natty n -> Natty m -> Either (m :< n) (n :<= m) -- forall n m, n <= m || m < n
Zy <=? m = Right Z_le_Z
Sy n <=? Zy = Left (S_le_S Z_le_Z)
Sy n <=? Sy m = either (Left . S_le_S) (Right . S_le_S) $ n <=? m
安全注射功能:
inject0Le :: Finny0 n p -> n :<= m -> Finny0 m p
inject0Le FZ _ = FZ
inject0Le (FS i) (S_le_S le) = FS (inject0Le i le)
即。如果n
是某个数字和n <= m
的上限,则m
也是此数字的上限。还有一个:
injectLe0 :: Finny n p -> n :<= m -> Finny0 m p
injectLe0 FZ (S_le_S le) = FZ
injectLe0 (FS i) (S_le_S le) = FS (injectLe0 i le)
现在代码如下:
getUpperBound :: Finny n m -> Natty n
getUpperBound = undefined
main = do
xs <- readLn :: IO [Int]
i <- read <$> getLine
putStrLn $
fromIntFinny i $ \i' ->
vfromList xs $ \xs' ->
case getUpperBound i' <=? vlength xs' of
Left _ -> "What should I do with this input?"
Right le -> show $ vtoList $ vtake (injectLe0 i' le) xs'
它编译,但getUpperBound
应该有什么定义?好吧,你无法定义它。 n
中的Finny n m
仅存在于类型级别,您无法提取或以某种方式获取。如果我们无法执行&#34; downcast&#34;,我们可以执行&#34; upcast&#34;:
fromIntNatty :: Int -> (forall n. Natty n -> b) -> b
fromIntNatty 0 f = f Zy
fromIntNatty n f = fromIntNatty (n - 1) (f . Sy)
fromNattyFinny0 :: Natty n -> (forall m. Finny0 n m -> b) -> b
fromNattyFinny0 Zy f = f FZ
fromNattyFinny0 (Sy n) f = fromNattyFinny0 n (f . FS)
进行比较:
fromIntFinny :: Int -> (forall n m. Finny n m -> b) -> b
fromIntFinny 0 f = f FZ
fromIntFinny n f = fromIntFinny (n - 1) (f . FS)
因此,fromIntFinny
中的延续对n
和m
变量进行了普遍量化,而fromNattyFinny0
中的延续通常仅针对m
量化。 fromNattyFinny0
收到Natty n
而不是Int
。
Finny0 n m
代替Finny n m
,因为FZ
是forall n m. Finny n m
的元素,而FZ
不一定是forall m. Finny n m
的元素对于某些n
,具体而言FZ
不是forall m. Finny 0 m
的元素(因此此类型无人居住)。
毕竟,我们可以加入fromIntNatty
和fromNattyFinny0
:
fromIntNattyFinny0 :: Int -> (forall n m. Natty n -> Finny0 n m -> b) -> b
fromIntNattyFinny0 n f = fromIntNatty n $ \n' -> fromNattyFinny0 n' (f n')
获得与@ pigworker的回答相同的结果:
unLenList :: LenList a -> (forall n. Natty n -> Vec n a -> t) -> t
unLenList (LenList xs) k = k natty xs
一些测试:
main = do
xs <- readLn :: IO [Int]
ns <- read <$> getLine
forM_ ns $ \n -> putStrLn $
fromIntNattyFinny0 n $ \n' i' ->
vfromList xs $ \xs' ->
case n' <=? vlength xs' of
Left _ -> "What should I do with this input?"
Right le -> show $ vtoList $ vtake (inject0Le i' le) xs'
的
[1,2,3,4,5,6]
[0,2,5,6,7,10]
返回
[]
[1,2]
[1,2,3,4,5]
[1,2,3,4,5,6]
What should I do with this input?
What should I do with this input?
修改强>
嗯,你无法定义它。芬尼的一个n只生活在这种类型中 等级,你不能提取它或得到某种方式。
那不是真的。拥有SingI n => Finny n m -> ...
后,我们可以将n
设为fromSing sing
。