【问题标题】:F# tree-building function causes stack overflow in Xamarin StudioF# 树构建函数导致 Xamarin Studio 中的堆栈溢出
【发布时间】:2017-07-08 06:02:10
【问题描述】:

我正在尝试在树结构中建立一些规则,使用逻辑门,即 andnotor 以及条件,例如属性 x 等于值 y。我先写了最明显的递归函数,它奏效了。然后,我从this post about generic tree foldingthis answer on stackoverflow 中得到提示,尝试编写一个不会导致堆栈溢出的连续传递样式。

它适用于小树(深度约为 1000),但不幸的是,当我使用 Xamarin Studio 在 Mac 上运行它时,使用大树时会导致堆栈溢出。谁能告诉我我是否误解了 F# 如何处理尾递归代码,或者这段代码是否不是尾递归?

The full sample is here.

let FoldTree andF orF notF leafV t data = 
    let rec Loop t cont = 
        match t with
        | AndGate (left, right)->
            Loop left  (fun lacc ->  
            Loop right (fun racc -> 
            cont (andF lacc racc))) 
        | OrGate (left, right)->
            Loop left  (fun lacc ->  
            Loop right (fun racc -> 
            cont (orF lacc racc))) 
        | NotGate exp ->
            Loop exp (fun acc -> cont (notF acc))
        | EqualsExpression(property,value) -> cont (leafV (property,value))
    Loop t id

let evaluateContinuationPassingStyle tree data = 
    FoldTree (&&) (||) (not) (fun (prop,value) -> data |> Map.find prop |> ((=) value)) tree data

【问题讨论】:

    标签: recursion xamarin f# functional-programming stack-overflow


    【解决方案1】:

    代码是尾递归的,你没看错。但问题在于 Mono。看,Mono 并不像官方的那样高质量的 .NET 实现。特别是,它不执行尾调用消除。就像,完全一样。

    对于最简单(也是最普遍)的自递归情况,这并不重要,因为编译器会更早地捕获它。 F# 编译器足够聪明,可以发现函数正在调用自己,找出在什么条件下调用,并将其转换为简洁的 while 循环,这样编译后的代码根本不会进行任何调用。

    但是当你的尾调用是对作为参数传递的函数时,编译器不能这样做,因为实际调用的函数直到运行时才知道。事实上,即使两个函数的相互递归也不能可靠地转换为循环。

    可能的解决方案:

    • 切换到 .NET Core。
    • 不要使用递归延续,而是使用累加器(可能不可能)。
    • 使用自递归并传递手动维护的延续堆栈。
    • 如果一切都失败了,请使用可变堆栈。

    【讨论】:

    • 这令人失望。花了很长时间认为这是我的实现问题。找到了这个bug report,看起来和这个匹配,最后的评论让它看起来不会很快得到解决。
    • Mono 即将消失。切换到 .NET Core。
    • @FyodorSoikin 我认为 Xamarin 的 Mono 不会很快消失。
    猜你喜欢
    • 2015-06-16
    • 2016-08-01
    • 2011-02-26
    • 2013-04-05
    • 2015-05-21
    • 2014-02-14
    相关资源
    最近更新 更多