【问题标题】:Is an infinitely recursive type useful?无限递归类型有用吗?
【发布时间】:2015-09-19 01:08:46
【问题描述】:

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

module BrokenRecursiveType where

data FooType = Foo FooType

main = print "it compiles!"

一开始我想,这有什么用?然后我想起 Haskell 是懒惰的,所以我可以定义一个类似下面的函数来使用它

allTheFoos = Foo allTheFoos

然后我想,那么这有什么用呢?

对于与FooType 类似的表单类型,是否有任何有价值的用例(构思或实际体验)?

【问题讨论】:

  • 一个猜想:每一种现存的语言要么允许你做一些无用的事情,要么限制得如此之大以至于它作为一个整体是无用的。
  • @DanielWagner,我同意。

标签: haskell ghc


【解决方案1】:

评估计数器

假设您可以使用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,因此也是 ⊥。

【讨论】:

    【解决方案2】:

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

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

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

    现在我们可以通过任何f :: * -> * 调制递归引用。但是这种扩展类型非常有用!事实上,它可以用来表达任何使用非递归数据类型的递归数据类型。 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)。

    【讨论】:

      【解决方案3】:

      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】:
        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. 如果您“不道德地”看待它,它类似于 Nat,但任何试图匹配“零”的函数都是非终止的。

        【讨论】:

        • allTheFoos底部,不是吗?
        • @ErikAllik:不,因为它的模式匹配总是会成功。
        • 哦,对了:只是一个无用价值的无限流。
        【解决方案5】:

        以下类型:

        newtype H a b = Fn {invoke :: H b a -> b}
        

        虽然与您的不完全相同,但具有相似的精神,但 Launchbury、Krstic 和 Sauerwein 已证明在 corouitining 方面有有趣的用途:https://arxiv.org/pdf/1309.5135.pdf

        【讨论】:

          猜你喜欢
          • 2023-03-14
          • 1970-01-01
          • 2018-07-29
          • 1970-01-01
          • 2014-04-20
          • 2012-04-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多