Haskell 无限递归类型有用吗?
最近我一直在尝试一个普遍的问题,GHC会允许我做什么?我惊讶地发现,它认为下面的程序是有效的Haskell 无限递归类型有用吗?,haskell,ghc,Haskell,Ghc,最近我一直在尝试一个普遍的问题,GHC会允许我做什么?我惊讶地发现,它认为下面的程序是有效的 module BrokenRecursiveType where data FooType = Foo FooType main = print "it compiles!" 起初我想,这有什么用?然后我想起Haskell是懒惰的,所以我可以定义一个如下的函数来使用它 allTheFoos = Foo allTheFoos 然后我想,这有什么用呢 对于类似于FooType的类型,是否有任何有价值
module BrokenRecursiveType where
data FooType = Foo FooType
main = print "it compiles!"
起初我想,这有什么用?然后我想起Haskell是懒惰的,所以我可以定义一个如下的函数来使用它
allTheFoos = Foo allTheFoos
然后我想,这有什么用呢
对于类似于FooType
的类型,是否有任何有价值的用例(构思或实际经验)?评估计数器
假设您可以使用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的值起作用
我并不是说这是特别有用的,也不是说这是实现这样一个功能的最佳方式
空洞型
顺便说一句,有一个类似的结构,即
newtype FooType' = Foo' FooType'
这很有用:这是定义除此之外没有值的void类型的一种方法⊥. 您仍然可以定义
allTheFoos' = Foo' allTheFoos'
与前面一样,但由于在操作上,Foo
什么也不做,这相当于x=x
,因此⊥.
data FooType = Foo FooType
allTheFoos = Foo allTheFoos
我认为有两种有用的方法来看待这种类型
第一种是“道德”方法,这是一种常见的方法,我们假装Haskell类型没有“底部”(非终止)值。从这个角度来看,FooType
是一种单位类型——一种只有一个值的类型,就像()
。这是因为如果您禁止底部,那么类型Foo
的唯一值就是您的allTheFoos
从“不道德”的角度来看(允许底部),FooType
要么是Foo
构造函数的无限塔,要么是底部有底部的Foo
构造函数的有限塔。这类似于这种类型的“道德”解释:
data Nat = Zero | Succ Nat
…但使用bottom而不是zero,这意味着您不能编写如下函数:
plus :: Nat -> Nat -> Nat
plus Zero y = y
plus (Succ x) y = Succ (x `plus` y)
那我们该怎么办?我认为结论是,FooType
不是一种真正有用的类型,因为:
()
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定义中删除了ANIT和ASum,则类型将与您的FootType同构(用AnInverse替换Foo)。让我们稍微扩展一下您的数据类型-让我们将递归封装到类型参数中:
data FooType f = Foo (f (FooType f))
(因此,您的原始数据类型将是FooType-Identity
)
现在我们可以通过任意f::*->*
来调节递归引用。但是这种扩展类型非常有用!事实上,它可以用来表示任何使用非递归数据类型的递归数据类型。一个定义为递归方案的well kwnown包,如下所示:
例如,如果我们定义
data List' a r = Cons' a r | Nil'
然后修复(列表'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
允许我们对递归数据类型定义许多通用操作,例如折叠/展开()。以下类型:
newtype H a b = Fn {invoke :: H b a -> b}
Launchbury、Krstic和Sauerwein的研究表明,尽管与您的语言不完全相同,但具有相似的精神,但它们在Corouiting方面有着有趣的用途:一个猜想:现有的每种语言要么允许您做一些没有用的事情,要么限制性太强,总体上没有用。@DanielWagner,我同意。
所有的oos
都是底部,不是吗?@ErikAllik:不,因为在上面进行模式匹配总是会成功的。哦,对了:只是一个无用价值的无限流。
newtype H a b = Fn {invoke :: H b a -> b}