Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Scala F#有尾部呼叫消除吗?_Scala_F#_Tail Recursion_Tail Call Optimization - Fatal编程技术网

Scala F#有尾部呼叫消除吗?

Scala F#有尾部呼叫消除吗?,scala,f#,tail-recursion,tail-call-optimization,Scala,F#,Tail Recursion,Tail Call Optimization,在这篇文章中,在前8分钟,Runar解释说Scala在尾部调用消除方面有问题,这让我想知道F#是否也有类似的问题?如果没有,为什么不呢?F#在尾部调用方面没有问题。它的作用如下: 如果您有一个单尾递归函数,编译器将生成一个带有变异的循环,因为这比生成.tail指令要快 在其他尾部调用位置(例如,当使用continuations或两个相互递归的函数时),它生成.tail指令,因此尾部调用由CLR处理 默认情况下,在VisualStudio中,尾部调用优化在调试模式下关闭,因为这使调试更容易(您可

在这篇文章中,在前8分钟,Runar解释说Scala在尾部调用消除方面有问题,这让我想知道F#是否也有类似的问题?如果没有,为什么不呢?

F#在尾部调用方面没有问题。它的作用如下:

  • 如果您有一个单尾递归函数,编译器将生成一个带有变异的循环,因为这比生成
    .tail
    指令要快

  • 在其他尾部调用位置(例如,当使用continuations或两个相互递归的函数时),它生成
    .tail
    指令,因此尾部调用由CLR处理

  • 默认情况下,在VisualStudio中,尾部调用优化在调试模式下关闭,因为这使调试更容易(您可以检查堆栈),但如果需要,可以在项目属性中打开它


过去,某些运行时(CLR x64和Mono)上的
.tail
指令曾经存在问题,但现在所有这些问题都已修复,一切正常

Scala中正确的尾部调用问题是一个工程权衡问题。将PTC添加到Scala很容易:只需在SLS中添加一句话。瞧,斯卡拉的PTC。从语言设计的角度来看,我们已经完成了

现在,糟糕的编译器编写者需要实现该规范。好吧,用PTC编译成一种语言很容易……但不幸的是,JVM字节码不是这样一种语言。好的,那么转到
怎么样?不。延续?不。例外情况(已知等同于延续)?啊,现在我们有进展了!因此,我们可以使用异常来实现PTC。或者,我们也可以完全不使用JVM调用堆栈来实现我们自己的堆栈

毕竟,JVM上有多个Scheme实现,它们都支持PTC。仅仅因为JVM不支持PTC,JVM上就不能有PTC,这是一个神话。毕竟,x86也没有它们,但尽管如此,在x86上运行的一些语言也有它们

那么,如果在JVM上实现PTC是可能的,那么Scala为什么没有它们呢?正如我上面所说,您可以使用异常或您自己的堆栈来实现它们。但是对控制流使用异常或实现自己的堆栈意味着期望JVM调用堆栈以某种方式显示的一切都将不再工作

特别是,您将失去几乎所有与Java工具生态系统(调试器、可视化工具、静态分析器)的互操作性。您还必须构建与Java库互操作的桥梁,这会很慢,因此您也会失去与Java库生态系统的互操作

但这是Scala的主要设计目标!这就是Scala没有PTC的原因

我用Clojure的设计师Rich Hickey的话说,我称之为“Hickey定理”,他曾在一次演讲中说“尾部调用、互操作、性能——选择两个。”

您还将向JIT编译器展示一些非常不寻常的字节码模式,它可能不知道如何很好地进行优化

如果要将F#移植到JVM,基本上必须做出正确的选择:放弃尾部调用(不能,因为它们是语言规范所要求的)、放弃互操作还是放弃性能?在.NET上,您可以拥有所有三个,因为F#中的尾部调用可以简单地编译为MSIL中的尾部调用。(尽管实际的转换比这更复杂,而且在某些情况下,MSIL中尾部调用的实现存在缺陷。)

这就提出了一个问题:为什么不向JVM添加尾部调用?这是非常困难的,因为JVM字节码中存在设计缺陷。设计者希望JVM字节码具有某些安全属性。但是,与设计JVM字节码语言的方式不同,首先你不能编写一个不安全的程序(比如说,在Java中,你不能编写一个违反指针安全的程序,因为该语言根本不允许你访问指针),JVM字节码本身是不安全的,需要一个单独的字节码验证器来确保其安全

字节码验证器基于堆栈检查,尾部调用会更改堆栈。因此,这两者很难协调,但如果没有字节码验证器,JVM根本无法工作。花了很长时间,一些非常聪明的人终于弄明白了如何在不丢失字节码验证器的情况下在JVM上实现尾部调用(请参见和),因此我们现在已经从一个开放的研究问题阶段转移到了“只是”一个开放的工程问题的阶段

请注意,Scala和其他一些语言确实具有方法内直尾递归。然而,从实现角度来看,这是相当无聊的:它只是一个
while
循环。大多数目标都有
while
循环或类似的东西,例如JVM有intra-method
GOTO
。斯卡拉也有一个新的蹦床,这是一种重新改造的蹦床。(请参阅此想法的更通用版本,它可以消除堆栈的所有使用,而不仅仅是在尾部调用中。)这可以用于在Scala中实现尾部调用算法,但这与JVM堆栈不兼容,即它看起来不像对其他语言或调试器的递归方法调用:

import scala.util.control.TailCalls._

def isEven(xs: List[Int]): TailRec[Boolean] =
  if (xs.isEmpty) done(true) else tailcall(isOdd(xs.tail))

def isOdd(xs: List[Int]): TailRec[Boolean] =
 if (xs.isEmpty) done(false) else tailcall(isEven(xs.tail))

isEven((1 to 100000).toList).result

def fib(n: Int): TailRec[Int] =
  if (n < 2) done(n) else for {
    x <- tailcall(fib(n - 1))
    y <- tailcall(fib(n - 2))
  } yield (x + y)

fib(40).result
导入scala.util.control.TailCalls_
def isEven(xs:List[Int]):TailRec[Boolean]=
if(xs.isEmpty)done(true)else tailcall(isOdd(xs.tail))
def isOdd(xs:List[Int]):TailRec[Boolean]=
if(xs.isEmpty)done(false)else tailcall(isEven(xs.tail))
isEven((1到100000)。toList。结果
def fib(n:Int):TailRec[Int]=
如果(n<2)完成(n)其他{

x事实证明,要获得正确的尾部调用,您必须在“发布模式”下编译,而不是在默认的“调试模式”下编译,或者打开项目属性,然后在“构建”菜单中,向下滚动并选中“生成尾部调用”。然后