在F#/Scala中优化相互递归的标准方法是什么?
-
04-10-2019 - |
题
这些语言“本地”不支持相互递归的功能优化,所以我想它一定是蹦床或.. he ..重写为循环)我会错过什么吗?
更新:看来我确实对FSHARP说谎,但是我只是在搜索时没有看到相互尾声的示例
解决方案
首先,f#本地支持相互递归的功能,因为它可以从中受益 tailcall
.net IL中可用的说明(MSDN)。但是,这有点棘手,可能对.NET的某些替代实现(例如紧凑型框架)不起作用,因此您有时可能需要手动处理此操作。
总的来说,我有几种处理方法:
蹦床 - 当递归深度太高并实现处理异常的顶级循环时(例外将带有信息以恢复呼叫)时,请引发异常。而不是异常,您还可以简单地返回一个值,以指定该函数应再次调用。
使用计时器放开 - 当递归深度太高时,您会创建一个计时器,并给它一个回调,该回调将在很短的时间后由计时器调用(计时器将继续递归,但使用的堆栈将丢弃)。
可以使用存储需要完成的工作的全局堆栈可以完成同样的事情。您不会安排计时器,而是将功能添加到堆栈中。在顶级,该程序将从堆栈中挑选功能并运行它们。
为了给出第一个技术的具体示例,在f#中,您可以写下:
type Result<´T> =
| Done of ´T
| Call of (unit -> ´T)
let rec factorial acc n =
if n = 0 then Done acc
else Call(fun () -> factorial (acc * n) (n + 1))
这也可用于相互递归的功能。命令式循环只会称呼 f
存储在功能中 Call(f)
直到它产生 Done
最终结果。我认为这可能是实施此问题的最干净的方法。
我敢肯定,还有其他用于处理此问题的复杂技术,但是这是我知道的两个(我使用的)。
其他提示
在Scala 2.8上 scala.util.control.TailCalls
:
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
只是为了付出f#相互递归时的代码方便:
let rec isOdd x =
if x = 1 then true else isEven (x-1)
and isEven x =
if x = 0 then true else isOdd (x-1)
printfn "%A" (isEven 10000000)
如果您没有尾巴调用(“调试”模式中的默认值,它将保留堆栈以进行更轻松的调试),这将堆叠在一起,但在使用尾部调用(默认为“发布”模式中的默认值)时运行正常。编译器默认情况下进行尾巴调用(请参阅 -TailCalls选项),大多数平台上的.NET实现荣誉。