无限递归类型有用吗?

时间:2015-07-01 18:14:21

标签: haskell ghc

最近我一直在试验一般性问题, GHC允许我做什么?我很惊讶地发现,它认为以下程序是有效的

module BrokenRecursiveType where

data FooType = Foo FooType

main = print "it compiles!"

起初我想,这有什么用?然后我记得Haskell很懒,所以我也许可以定义一个像下面这样的函数来使用它

allTheFoos = Foo allTheFoos

然后我想,所以这有用吗?

对于FooType类似表单的类型,是否有任何有价值的用例(想出或实际经历过)?

5 个答案:

答案 0 :(得分:9)

评估计数器

假设您可以使用FooType来提前中止递归函数:例如,请使用以下代码:

foo _ 0 = 1
foo (Foo x) n = n * foo x (n-1)

如果您致电foo allTheFoos,那么您将获得简单的阶乘功能。但是您可以传递FooType类型的不同值,例如

atMostFiveSteps = Foo (Foo (Foo (Foo (Foo (error "out of steps")))))

然后foo atMostFiveSteps仅适用于小于6的值。

我不是说这是特别有用的,也不是说这是实现这样一个功能的最好方法......

空白类型

BTW,有类似的结构,即

newtype FooType' = Foo' FooType'

这很有用:这是定义除了⊥之外没有值的void类型的一种方法。你仍然可以定义

allTheFoos' = Foo' allTheFoos'
和以前一样

,但由于操作上Foo什么也没做,这相当于x = x,因此也是⊥。

答案 1 :(得分:4)

让我们稍微扩展您的数据类型 - 让我们将递归包装到类型参数中:

data FooType f = Foo (f (FooType f))

(因此您的原始数据类型为FooType Identity)。

现在我们可以通过任何f :: * -> *来调制递归引用。但这种扩展类型非常有用!实际上,它可以用于表示使用非递归数据类型的任何递归数据类型。其中定义的一个kwnown包是 recursion-schemes ,如Fix

newtype Fix f = Fix (f (Fix f))

例如,如果我们定义

data List' a r = Cons' a r | Nil'

然后Fix (List' a)[a]同构:

nil :: Fix (List' a)
nil = Fix Nil'

cons :: a -> Fix (List' a) -> Fix (List' a)
cons x xs = Fix (Cons' x xs)

此外,Fix允许我们在递归数据类型上定义许多泛型操作,例如折叠/展开(catamorphisms/anamorphisms)。

答案 2 :(得分:1)

data FooType = Foo FooType

allTheFoos = Foo allTheFoos

我认为有两种有用的方法来看待这种类型。

首先是"道德"方式 - 我们假装Haskell类型没有"底部" (非终止)值。从这个角度来看,FooType单元类型 - 只有一个值的类型,就像()一样。这是因为如果您禁止底部,那么Foo类型的唯一值就是您的allTheFoos

来自"不道德"透视(允许底部),FooType或者是Foo构造函数的无限塔,或者是Foo构造函数的有限塔,其底部是底部。这类似于"道德"这种解释:

data Nat = Zero | Succ Nat

...但是底部而不是零,这意味着你不能写出像这样的函数:

plus :: Nat -> Nat -> Nat
plus Zero y = y
plus (Succ x) y = Succ (x `plus` y)

离开我们的地方?我认为结论是FooType并不是真正有用的类型,因为:

  1. 如果你看一下"道德"它相当于()
  2. 如果你看一下" immorally"它类似于Nat但是任何试图匹配"零"的函数。没有终止。

答案 3 :(得分:1)

您的FooType的扩展名可以是抽象语法树。使用仅具有整数,求和和反转的简单示例语言,类型定义将是

data Exp = AnInt Integer
         | AnInverse Exp
         | ASum Exp Exp

以下所有内容都是Exp实例:

AnInt 2  -- 2
AnInverse ( AnInt 2 )  -- 1 / 2
AnInverse ( ASum ( AnInt 2 ) ( AnInt 3 ) )  -- 1 / ( 2 + 3 )
AnInverse ( ASum 1 ( AnInverse 2 ) )  -- 1 / ( 1 + 1 / 2 )

如果我们从Exp定义中删除了AnInt和ASum,那么该类型将与您的FooType同构(AnInverse替换Foo)。

答案 4 :(得分:0)

以下类型:

newtype H a b = Fn {invoke :: H b a -> b}
虽然与你的不完全相同,但具有相似的精神,但Launchbury,Krstic和Sauerwein已经展示了有关corouitining的有趣用法:https://arxiv.org/pdf/1309.5135.pdf