我是类型级编程的新手,我无法推理甚至非常简单的类型级程序。
我有这段代码:
data Nat = Zero | Succ Nat
type family n + m where
Zero + m = m -- This is the base case of my type level recursive function.
现在,我有两种关于递归案例的变体,其中只有一种没有UndecidableInstances
的类型检查:
Succ n + m = n + Succ m -- Typechecks with UndecidableInstances.
Succ n + m = Succ (n + m) -- Typechecks without UndecidableInstances.
我理解UndecidableInstances
允许我在类型级别上使用一般递归,而不是单独使用结构递归,这将保证类型检查器终止。但实际上,这两个定义似乎编译得同样好,并且在使用repl中的:kind!
进行验证时,对于较小的数字,可以给出理智的类型。
我可以确定这两个定义在所有方面都是等价的,还是存在极端情况?我应该相互测试+
的这些实现,还是使用某种理论?
答案 0 :(得分:2)
假设
foo :: T (Succ n) -> Bool
bar :: T n -> T m -> T (n + m)
然后,
baz :: T (Succ a) -> String
baz x | foo (bar x x) = "A"
| otherwise = "B"
只会输入检查是否使用
定义了+
Succ n + m = Succ (n + m)
如果我们使用
Succ n + m = n + Succ m
然后,在baz
的类型检查期间,我们会发现bar x x :: a + Succ (Succ a)
,但这不是foo
的参数的正确形式。
总结:仅考虑“ground”Nat
类型是不够的。在类型检查期间,我们还遇到具有自由类型变量(即非基础)的类型表达式,如Succ a + Succ a
,这将根据等式规则进行简化。我们也需要适用于此类案例的规则。
答案 1 :(得分:2)
正如其他人所指出的那样,这两个定义并不相同。两者都没有比另一个更普遍,并且认为一方或另一方明显优越是一种延伸。
正如@Benjamin Hodgson指出的那样,定义的选择会影响编写代码的难易程度,但我补充一点,通常每个选项都会使代码更容易,其他代码更难。
这是一个具体的例子。假设您有一个矢量类型:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
data Nat = Zero | Succ Nat
-- A vector type
data Vec n a where
V0 :: Vec Zero a
(:>) :: a -> Vec n a -> Vec (Succ n) a
infixr 5 :>
让我们从使用(+)
的“漂亮”版本开始,这个版本不需要不可判定的实例。请注意,我已切换m
和n
的顺序,因为我感到困惑:
type family m + n where
Zero + m = m
Succ m + n = Succ (m + n)
如果我们尝试为矢量实现(++)
,我们会发现它很简单:
vappend :: Vec m a -> Vec n a -> Vec (m + n) a
vappend V0 ys = ys
vappend (x :> xs) ys = x :> vappend xs ys
另一方面,请考虑以下列表功能:
rev :: [a] -> [a] -> [a]
rev ys [] = ys
rev ys (x:xs) = rev (x:ys) xs
可能用于定义reverse
的版本:
reverse' = rev []
如果我们尝试实施vrev
,我们会遇到麻烦:
vrev :: Vec m a -> Vec n a -> Vec (n + m) a
vrev ys V0 = ys
vrev ys (x :> xs) = vrev (x :> ys) xs
第一个案例类型检查没问题,但第二个案件因GHC无法推断的投诉而失败:
(n1 + 'Succ m) ~ 'Succ (n1 + m)
尽管这句话“显然”是真的。
在签名中用n + m
替换m + n
会让事情变得更糟 - 两种情况都不会进行类型检查。
另一方面,如果用不可判定的版本替换+
定义:
Succ m + n = m + Succ n
你会发现vrev
类型检查正常而vappend
没有!
那么,解决方案是什么?好吧,通常人们会选择在大多数情况下看起来最方便的定义,然后使用各种技术来处理“硬”案例。如果您选择:
Succ m + n = Succ (m + n)
定义,你会成为一个好公司。例如,Hasochism paper和singletons-nats
包中使用的那个。