Haskell 无限递归类型有用吗?

Haskell 无限递归类型有用吗?,haskell,ghc,Haskell,Ghc,最近我一直在尝试一个普遍的问题,GHC会允许我做什么?我惊讶地发现,它认为下面的程序是有效的 module BrokenRecursiveType where data FooType = Foo FooType main = print "it compiles!" 起初我想,这有什么用?然后我想起Haskell是懒惰的,所以我可以定义一个如下的函数来使用它 allTheFoos = Foo allTheFoos 然后我想,这有什么用呢 对于类似于FooType的类型,是否有任何有价值

最近我一直在尝试一个普遍的问题,GHC会允许我做什么?我惊讶地发现,它认为下面的程序是有效的

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
不是一种真正有用的类型,因为:

  • 如果你从“道德”的角度来看,它相当于
    ()
  • 如果你“不道德地”看它,它类似于Nat,但是任何试图匹配“零”的函数都是不终止的

  • 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}