微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么 Haskell 不需要蹦床?

如何解决为什么 Haskell 不需要蹦床?

由于 Scala 开发人员正在学习 IO Monad,因此在无法进行尾调用优化的情况下,一般来说,蹦床技术对于递归来说是必需的,我想知道 Haskell 是如何在本机上避免它的。

我知道 Haskell 是一种懒惰的语言,但是我想知道是否有人可以进一步阐述。

例如,为什么 ForeverM stackoverflow 在 Scala 中没有?嗯,我可以回答蹦床,我可以在库和博客中找到执行此操作的实际代码。我实际上自己实现了一个基本的蹦床来学习。

它在 Haskell 中是如何发生的?有没有办法稍微解开懒惰,给出一些指示,也许还有文档可以帮助更好地理解它?

sealed trait IO[A] {

.....


  def flatMap[B](f: A => IO[B]): IO[B] =
    FlatMap[A,B](this,f) // we do not interpret the `flatMap` here,just return it as a value
  def map[B](f: A => B): IO[B] =
    flatMap[B](f andThen (Return(_)))

}
case class Return[A](a: A) extends IO[A]
case class Suspend[A](resume: () => A) extends IO[A]
case class FlatMap[A,B](sub: IO[A],k: A => IO[B]) extends IO[B]

......

@annotation.tailrec
def run[A](io: IO[A]): A = io match {
  case Return(a) => a
  case Suspend(r) => r()
  case FlatMap(x,f) => x match {
    case Return(a) => run(f (a))
    case Suspend(r) => run(f( r()))
    case FlatMap(y,g) => run(y flatMap (a => g(a) flatMap f))
  }
}

解决方法

函数式编程通常需要消除尾调用(否则函数调用的深层链会溢出堆栈)。例如,考虑一下偶数/奇数分类器的这种(非常低效)实现:

def even(i: Int): Boolean =
  if (i == 0) true
  else if (i > 0) odd(i - 1)
  else odd(i + 1)

def odd(i: Int): Boolean =
  if (i == 0) false
  else if (i > 0) even(i - 1)
  else even(i + 1)

evenodd 中,每个分支都是不进行函数调用的简单表达式(在本例中为 truefalse)或tail-call:被调用函数的值不操作就返回。

如果不消除尾调用,则(可能会以无限长的周期递归)调用必须使用消耗内存的堆栈来实现,因为调用者可能会对结果做一些事情。尾调用消除依赖于观察调用者不对结果做任何事情,因此被调用的函数可以有效地替换堆栈上的调用者。

Haskell 和基本上所有其他 post-Scheme 函数式语言运行时都实现了广义的尾调用消除:尾调用变成了无条件跳转(想想 GOTO)。著名的 series of Steele and Sussman papers(不幸的是,PDF 没有存档,但您可以搜索,例如 AIM-443(可能需要mitsteelesussman )) 被称为“Lambda: The Ultimate”(它启发了一个编程语言论坛的名称)经历了尾调用消除的含义,以及这意味着函数式编程对于解决现实世界的计算问题实际上是可行的。>

然而,Scala 主要针对 Java 虚拟机,其规范(通过设计)有效地禁止了广义的尾调用消除,并且其指令集限制无条件跳转不跨越方法的边界。在某些有限的上下文中(基本上是一个方法的递归调用,编译器可以绝对确定正在调用什么实现),Scala 编译器在发出 Java 字节码之前执行尾调用消除(理论上可以想象 Scala Native 可以执行泛化尾调用消除,但这会导致 JVM 和 JS Scala 的一些语义中断(一些 JavaScript 运行时执行通用的尾调用消除,但据我所知不是 V8))。您可能熟悉的 @tailrec 注释强制要求编译器能够执行尾调用消除。

蹦床是一种在运行时模拟编译时尾调用消除的低级技术,尤其是在 C 或 Scala 等语言中。由于 Haskell 在编译时执行了尾调用消除,因此不需要 Trampoline 的复杂性(以及将高级代码编写为 continuation-passing 风格的要求)。

可以说,您可以将 Haskell 程序中的 CPU(或运行时本身,如果转换为 JS)视为实现蹦床。

,

蹦床不是尾叫的唯一解决方案。 Scala 需要蹦床,因为它运行在 JVM 上,带有 Java 运行时。 Scala 语言开发人员无法准确选择运行时的运行方式,也无法选择二进制格式。因为他们使用 JVM,所以他们必须忍受 JVM 针对 Java 而不是针对 Scala 优化的各种方式。

Haskell 没有这个限制,因为它有自己的运行时,它自己的二进制格式等。它可以根据 Haskell 语言的语言级构造精确选择如何在运行时设置堆栈 --- 不是,Java 的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。