为什么归纳数据类型禁止类型如`data Bad a = C(Bad a - > a)`其中类型递归发生在 - >?之前?

Pet*_*lák 16 haskell y-combinator recursive-datastructures algebraic-data-types agda

关于归纳数据类型和模式匹配的 Agda手册:

为确保正常化,归纳事件必须出现在严格的正位置.例如,不允许以下数据类型:

data Bad : Set where
  bad : (Bad ? Bad) ? Bad
Run Code Online (Sandbox Code Playgroud)

因为构造函数的参数中存在负面的Bad.

为什么归纳数据类型需要此要求?

Dan*_*ner 14

您提供的数据类型是特殊的,因为它是无类型 lambda演算的嵌入.

data Bad : Set where
  bad : (Bad ? Bad) ? Bad

unbad : Bad ? (Bad ? Bad)
unbad (bad f) = f
Run Code Online (Sandbox Code Playgroud)

我们来看看如何.回想一下,无类型的lambda演算有以下术语:

 e := x | \x. e | e e'
Run Code Online (Sandbox Code Playgroud)

我们可以定义[[e]]从无类型lambda演算术语到Agda术语类型的翻译Bad(尽管不在Agda中):

[[x]]     = x
[[\x. e]] = bad (\x -> [[e]])
[[e e']]  = unbad [[e]] [[e']]
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用您最喜欢的非终止无类型lambda术语来生成类型的非终止术语Bad.例如,我们可以转换(\x. x x) (\x. x x)Bad以下类型的非终止表达式:

unbad (bad (\x -> unbad x x)) (bad (\x -> unbad x x))
Run Code Online (Sandbox Code Playgroud)

虽然类型恰好是这个参数的一种特别方便的形式,但它可以通过一些工作推广到任何具有负递归递归的数据类型.

  • @PetrPudlák所以,我只是和我的同事聊天,他们是比我更好的理论家.一致认为,这个"坏"不会产生一个类型的术语.a`(这是你真正关心的;递归只是达到目的的一种方式).这个论点是这样的:你可以构建一个Agda的集合论模型; 那么你可以在该模型中添加一个'Bad`作为单元素集的解释; 由于结果模型中仍然存在无人居住的类型,因此没有将循环"坏"术语转换为另一种类型的循环术语的功能. (4认同)

Pet*_*lák 12

在Turner,DA(2004-07-28),Total Functional Programming,sect中给出了这种数据类型如何允许我们居住在任何类型中的示例.规则2中的 3.1,第758页:类型递归必须是协变的."


让我们使用Haskell做一个更详细的例子.我们将从"坏"递归数据类型开始

data Bad a = C (Bad a -> a)
Run Code Online (Sandbox Code Playgroud)

并且从中构造Y组合子而没有任何其他形式的递归.这意味着拥有这样的数据类型允许我们构造任何类型的递归,或通过无限递归来居住任何类型.

无类型lambda演算中的Y组合子定义为

Y = ?f.(?x.f (x x)) (?x.f (x x))
Run Code Online (Sandbox Code Playgroud)

关键是我们适用x于自己x x.在类型语言中,这不是直接可能的,因为没有x可能有效的类型.但是我们的Bad数据类型允许这个模数添加/删除构造函数:

selfApp :: Bad a -> a
selfApp (x@(C x')) = x' x
Run Code Online (Sandbox Code Playgroud)

采取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
Run Code Online (Sandbox Code Playgroud)

请注意,既不是selfApp也不yc是递归的,也不会对函数自身进行递归调用.递归仅出现在我们的递归数据类型中.

我们可以检查构造的组合子确实做了应有的事情.我们可以做一个无限循环:

loop :: a
loop = yc id
Run Code Online (Sandbox Code Playgroud)

或计算让我们说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
Run Code Online (Sandbox Code Playgroud)