Stack overflow exception when using pipes in tail-recursive function

余生长醉 提交于 2020-01-03 07:33:07

问题


I have a naive implementation of a gameloop

let gameLoop gamestate =     
    let rec innerLoop prev gamestate =
        let now = getTicks()
        let delta = now - prev
        gamestate 
        |> readInput delta
        |> update delta
        |> render delta
        |> innerLoop delta             

    innerLoop 0L gamestate 

This implementation throws a stackoverflowexception. In my mind this should be tail recursive. I could make a work around like this

let gameLoop gamestate =     
    let rec innerLoop prev gamestate =
        let now = getTicks()
        let delta = now - prev
        let newState = gamestate 
            |> readInput delta
            |> update delta
            |> render delta

        innerLoop now newState

    innerLoop 0L gamestate  

So my question is why the first code example throws a stackoverflow exception.


回答1:


I think the answer is the same as in the thread Vandroiy links: when you have

a
|> f b

then in debug mode the compiler may compile this like a very literal interpretation of

(f b) a

and explicitly calculate f b in one step and apply it to a in a second step. The call with argument a is still a tail call, but if the compiler doesn't emit the tail. opcode prefix (because tailcalls are turned off, as they are by default in debug mode), then you'll grow the stack with the explicit call and eventually get a stack overflow.

On the other hand, if you write

f b a

directly then this doesn't happen: the compiler does not partially apply f, and instead will recognize that this is a direct recursive call and optimize it into a loop (even in debug mode).




回答2:


I think this is the explanation, though I encourage F# compiler experts to weigh in if I'm off-base:

The first example is not tail-recursive because the expression in tail position is a call to |>, not a call to innerLoop.

Recalling that |> is defined as

let (|>) x f = f x

if we desugar the pipeline syntax a little bit, when you call

gamestate 
    |> readInput delta
    |> update delta
    |> render delta
    |> innerLoop delta

you're effectively calling:

|> (innerLoop delta) (|> (render delta) (|> (update delta) (|> (readInput delta) gamestate)))

as your body expression in the recursive function.

The infix notation obscures this a bit, making it look like innerLoop is in tail position.



来源:https://stackoverflow.com/questions/40163815/stack-overflow-exception-when-using-pipes-in-tail-recursive-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!