3

I was planning on writing a simple game to test out my understanding of functional programming. The functional way of doing the main loop, would be recursing it, but won't this eat up memory as more and more stack frames are generated?

Thanks

An example from How can you do anything useful without mutable state?

// imperative version
pacman = new pacman(0, 0)
while true
    if key = UP then pacman.y++
    elif key = DOWN then pacman.y--
    elif key = LEFT then pacman.x--
    elif key = UP then pacman.x++
    render(pacman)

// functional version
let rec loop pacman =
    render(pacman)
    let x, y = switch(key)
        case LEFT: pacman.x - 1, pacman.y
        case RIGHT: pacman.x + 1, pacman.y
        case UP: pacman.x, pacman.y - 1
        case DOWN: pacman.x, pacman.y + 1
    loop(new pacman(x, y))
Community
  • 1
  • 1
Peter
  • 1,169
  • 2
  • 12
  • 24

2 Answers2

5

You have implemented your loop function using tail recursion, i.e. the recursive call to loop is the last thing in the function. This allows the compiler/interpreter (depending on the language) to pop the current stack frame immediately and replace it with the recursive call's frame.

Long story short, the way you have implemented it, there will be no stack overflow, and loop can run for as long as needed.

waldrumpus
  • 2,460
  • 14
  • 39
  • For further reading on this see http://en.wikipedia.org/wiki/Continuation-passing_style – Darius Aug 22 '12 at 11:38
  • Ok, so this can just be assumed to happen in most languages (even imperative)? Are there any ways that I could do this if the recursive call wasn't the last thing? – Peter Aug 22 '12 at 13:13
  • 1
    @Peter This optimization is not generally performed for imperative languages. It is also not performed for Clojure, but as an alternative Clojure offers the `recur` form which you can use instead of the name of the current function to recurse without consuming stack space (`recur` can only be used in a tail position). If the recursive call is not in a tail position, you can only avoid consuming stack space by rewriting it, so that it is in the tail position (note that if you can't trivially do so, you also wouldn't be able to trivially express the logic as a loop without an explicit stack). – sepp2k Aug 22 '12 at 13:28
  • 1
    @Peter The important thing is that the caller must return immediately after calling the callee. Something like this is also tail-recursive: `if x then loop(...) else loop(...)`; but *not* this: `if x then (loop(...); 17) else loop(...)` – waldrumpus Aug 22 '12 at 13:30
0

Recursion is the new iteration :) Blog plug: http://blogs.msdn.com/b/ashleyf/archive/2010/02/06/recursion-is-the-new-iteration.aspx

You say you're using Clojure and would also be keen on knowing for F#.

It turns out that JVM-based languages (Java, Scala, Clojure, ...) can't support tail call optimization at the VM level and so have workarounds such as Clojure's recur. CLR-based languages (F#, C#, VB, ...) can and do use a .tail marker in the IL to cause early discarding of the stack frame.

Tail call optimization can make debugging a pain, so F# for example doesn't do it in debug builds (but does in release). There's a checkbox in project settings to enable in debug.

AshleyF
  • 3,502
  • 1
  • 22
  • 23
  • Just a note, JVM languages can't support general tail *call* optimisation, but can support the more restricted tail *recursion* optimisation. Scala does. The reason for having an explicit tail recursion operator like `recur` is that if something you expect to be a tail recursion actually ends up not being so, then you get an error alerting you to this fact, whereas if that happens with implicit tail recursion elimination you may (eventually) get a bug report about a stack overflow that shouldn't have happened. – Ben Aug 22 '12 at 23:58
  • Yes, certainly you can do a branch rather than a call for simple (non-mutual) recursion. It's when you want to do a call and eliminate the stack frame that you're out of luck with the JVM. – AshleyF Sep 01 '12 at 16:19