使用最终工作流程时,缺少尾调用优化是一个障碍吗?

Joh*_*Joh 10 f# tail-recursion tail-call-optimization

我正在使用F#规范的最终工作流程的修改版本,以便在Xbox上进行开发.看来,Xbox上的.net框架不支持尾调用.因此,我必须在编译时禁用尾调用优化.

虽然最初似乎这种限制会阻止在计算表达式中使用任何形式的循环,但我最初认为"步进"可以避免这个问题:计算表达式中的递归函数f不直接调用自身,而是返回一个包含lambda的最终值,该lambda调用f.

实验表明我对while循环是正确的(它们在计算表达式中使用时不会导致堆栈溢出),但不是关于递归函数.

为了澄清,这有效:

// Wait until "start" or "A" is pressed on one of the gamepads.
// Set "player" when that happens.
let player : PlayerIndex option ref = ref None
while (!player).IsNone do
    for p in all_players do
        let state = GamePad.GetState(p)
        if state.IsConnected
            && (state.Buttons.Start = ButtonState.Pressed
                || state.Buttons.A = ButtonState.Pressed) then
            player := Some p
    do! sys.WaitNextFrame()
Run Code Online (Sandbox Code Playgroud)

这会导致堆栈溢出:

// Wait until "start" is pressed on the controlling gamepad.
let rec wait() = task {
    input.Update()
    if not (input.IsStartPressed()) then
        do! sys.WaitNextFrame()
        do! wait()
}
Run Code Online (Sandbox Code Playgroud)

在第二种情况下,堆栈跟踪显示了一系列对"bind @ 17-1"的调用,其代码如下所示.出现在堆栈跟踪中的行号是第17行.

let rec bind k e =
    match e with
    | Completed r ->
        Running(fun () -> k r)
    | Running f ->
        Running(fun () -> f() |> bind k)  // line 17
    | Blocked(dt, f) ->
        Blocked(dt, fun () -> f() |> bind k)
    | BlockedNextFrame f ->
        BlockedNextFrame(fun () -> f() |> bind k)
    | Yield f ->
        Yield(fun () -> f() |> bind k)
Run Code Online (Sandbox Code Playgroud)

我哪里错了?我的理由是关于"steppable recursion"是无害的,堆栈溢出不正确吗?我的bind实现有问题吗?

哦,我的脑袋!继续传递递归让我感到头痛......

编辑:"steppable递归是无害的wrt堆栈溢出"已经有一个名字,我刚学到.它被称为蹦床.

des*_*sco 11

替换最后做的!回报!:

// Wait until "start" is pressed on the controlling gamepad.
let rec wait() = task {
    input.Update()
    if not (input.IsStartPressed()) then
       do! sys.WaitNextFrame()
       return! wait()
}
Run Code Online (Sandbox Code Playgroud)

编辑

根据F#规范,做!wait()将转换为Bind(wait(),fun() - > Zero()),因此每次递归调用都会增加Bind嵌套的级别(正如您在stacktrace中看到的那样)

在相反的回报!wait()将立即返回可在下一步消耗的新"最终"计算.


gra*_*bot 6

了解正在发生的事情的一种方法是查看类型签名.

type TaskBuilder() =
    // do!
    // Eventually<'a> * ('a -> Eventually<'b>) -> Eventually<'b>
    member x.Bind(e, f) = bind f e

    // return!
    // Eventually<'a> -> Eventually<'a>
    member x.ReturnFrom(r : Eventually<_>) = r

    // return
    // 'a -> Eventually<'a>
    member x.Return(r) = result r


let result r = Completed(r)
Run Code Online (Sandbox Code Playgroud)

f#中的所有函数都必须返回一些东西.所以下面的代码

let rec wait() = task {
    input.Update()
    if not (input.IsStartPressed()) then
        do! sys.WaitNextFrame()
        do! wait()
}
Run Code Online (Sandbox Code Playgroud)

相当于

let rec wait() = task {
    input.Update()
    if not (input.IsStartPressed()) then
        do! sys.WaitNextFrame()
        do! wait()
        return ()
}
Run Code Online (Sandbox Code Playgroud)

如果我们看一下Return的定义,它会调用结果,然后返回一个Completed(r).

我做了两个小测试任务.

let test7() =
    let sch = new Scheduler()
    let sys = new Environment(sch)

    let rec hop i = task {
        printfn "%i: Hop!" i
        //do! sys.Wait(1.0f)
        if i > 0 then
            do! hop (i - 1)
            return ()
    }

    runAllFixed sch 0.1f [| hop 3 |]

let test8() =
    let sch = new Scheduler()
    let sys = new Environment(sch)

    let rec hop i = task {
        printfn "%i: Hop!" i
        //do! sys.Wait(1.0f)
        if i > 0 then
            return! hop (i - 1)
    }

    runAllFixed sch 0.1f [| hop 3 |]

test7()
printfn "\n" 
test8()
Run Code Online (Sandbox Code Playgroud)

有一些仪器打印.

Delay 3: Hop!
Delay Bind Running 2: Hop!
Delay Bind Running Running 1: Hop!
Delay Bind Running Running Running 0: Hop!
Zero Completed Running Running Return Completed Running Return Completed Return


Delay 3: Hop!
Delay ReturnFrom 2: Hop!
Delay ReturnFrom 1: Hop!
Delay ReturnFrom 0: Hop!
Zero
Run Code Online (Sandbox Code Playgroud)

关于计算表达式调用的MSDN doc .