我正在使用F#规范的最终工作流程的修改版本,以便在Xbox上进行开发。看来,Xbox上的.net框架不支持尾调用。因此,我必须在编译时禁用尾调用优化。
虽然最初似乎这个限制会阻止在计算表达式中使用任何形式的循环,但我最初认为“步进”可以避免这个问题:计算表达式中的递归函数f不直接调用自身而是返回一个包含lambda的Eventually值,该值调用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()
这会导致堆栈溢出:
// 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()
}
在第二种情况下,堆栈跟踪显示了对“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)
我哪里错了?我的理由是“可行的递归”是无害的w.r.t.堆栈溢出不正确?我的bind实现有问题吗?
哦,我的脑袋!递归传递给我带来了麻烦......
编辑:“可执行的递归是无害的w.r.t.堆栈溢出”已经有了一个名字,我刚学会了。它被称为蹦床。答案 0 :(得分: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()
}
修改强>
根据F#规范,做! wait()将转换为Bind(wait(),fun() - > Zero()),因此每次递归调用都会增加Bind嵌套的级别(正如您在stacktrace中看到的那样)
反对返回! wait()将立即返回可在下一步消耗的新“最终”计算。
答案 1 :(得分: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)
f#中的所有函数都必须返回一些东西。所以下面的代码
let rec wait() = task {
input.Update()
if not (input.IsStartPressed()) then
do! sys.WaitNextFrame()
do! wait()
}
相当于
let rec wait() = task {
input.Update()
if not (input.IsStartPressed()) then
do! sys.WaitNextFrame()
do! wait()
return ()
}
如果我们看一下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()
用一些仪器打印。
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
MSDN doc Computation Expression来电。