Agda手册中关于归纳数据类型和模式匹配的部分指出:
为了确保规范性,归纳出现必须在严格正位置。例如,下面的数据类型是不允许的:
data Bad : Set where bad : (Bad → Bad) → Bad
由于构造函数的参数中存在Bad的负面出现,因此这个要求对归纳数据类型为什么是必要的?
Agda手册中关于归纳数据类型和模式匹配的部分指出:
为了确保规范性,归纳出现必须在严格正位置。例如,下面的数据类型是不允许的:
data Bad : Set where bad : (Bad → Bad) → Bad
由于构造函数的参数中存在Bad的负面出现,因此这个要求对归纳数据类型为什么是必要的?
你给出的数据类型是特殊的,因为它是未类型化 lambda 演算的嵌入。
data Bad : Set where
bad : (Bad → Bad) → Bad
unbad : Bad → (Bad → Bad)
unbad (bad f) = f
让我们看看如何做。回想一下,无类型的 λ演算有以下术语:
e := x | \x. e | e e'
我们可以定义一个从未类型化的lambda演算术语到类型为Bad
的Agda术语的翻译 [[e]]
(尽管不能在Agda中实现):
[[x]] = x
[[\x. e]] = bad (\x -> [[e]])
[[e e']] = unbad [[e]] [[e']]
现在,您可以使用自己喜欢的非终止无类型 lambda 术语来生成类型为 Bad
的非终止术语。例如,我们可以将 (\x. x x) (\x. x x)
转换为下面的类型为 Bad
的非终止表达式:
unbad (bad (\x -> unbad x x)) (bad (\x -> unbad x x))
尽管这种类型恰巧是这个参数的特别方便的形式,但只要稍加努力就可以将其推广到任何具有递归负数出现的数据类型。这样的数据类型允许我们在任何类型中使用,例如在Turner, D.A. (2004-07-28)的函数式编程总体论中提到了一个例子,在第3.1节、758页中的规则2:类型递归必须是协变的。
"data Bad a = C (Bad a -> a)
Y = λf.(λx.f (x x)) (λx.f (x x))
selfApp :: Bad a -> a
selfApp (x@(C x')) = x' x
考虑 x :: Bad a
,我们可以拆开它的构造函数,并将其中的函数应用到 x
本身。一旦我们知道如何做到这一点,构建 Y 组合子就很容易了:
yc :: (a -> a) -> a
yc f = let fxx = C (\x -> f (selfApp x)) -- this is the λx.f (x x) part of Y
in selfApp fxx
selfApp
也不是 yc
是递归的,没有将函数递归调用到它本身。 递归仅出现在我们的递归数据类型中。loop :: a
loop = yc id
或者计算,比如说GCD:
gcd' :: Int -> Int -> Int
gcd' = yc gcd0
where
gcd0 :: (Int -> Int -> Int) -> (Int -> Int -> Int)
gcd0 rec a b | c == 0 = b
| otherwise = rec b c
where c = a `mod` b
Bad
不会产生一个类型为forall a. a
的术语(这才是你真正关心的;递归只是到达这个目标的一种手段)。他们的观点是这样的:你可以构建一个 Agda 的集合论模型;然后,在该模型中增加一个解释Bad
为一个单元素集合的部分;由于在结果模型中仍然存在无人居住的类型,因此没有函数将循环的Bad
术语转化为另一种类型的循环术语。 - Daniel Wagner