views:

527

answers:

3

I like to define sequences recursively as follows:

let rec startFrom x =
    seq {
        yield x;
        yield! startFrom (x + 1)
    }

I'm not sure if recursive sequences like this should be used in practice. The yield! appears to be tail recursive, but I'm not 100% sure since its being called from inside another IEnumerable. From my point of view, the code creates an instance of IEnumerable on each call without closing it, which would actually make this function leak memory as well.

Will this function leak memory? For that matter is it even "tail recursive"?

[Edit to add]: I'm fumbling around with NProf for an answer, but I think it would be helpful to get a technical explanation regarding the implementation of recursive sequences on SO.

A: 

.NET applications don't "leak" memory in this way. Even if you are creating many objects a garbage collection will free any objects that have no roots to the application itself.

Memory leaks in .NET usually come in the form of unmanaged resources that you are using in your application (database connections, memory streams, etc.). Instances like this in which you create multiple objects and then abandon them are not considered a memory leak since the garbage collector is able to free the memory.

Andrew Hare
The garbage collection makes no guarantee on when it is going to collect these objects, however. So no, this is not a memory leak practice but it's not a good memory economy practice either.
marr75
@marr75 - Good point and good distinction!
Andrew Hare
It's also possible that the IEnumerable that's generated could be structured so that earlier elements don't become eligible for garbage collection even after they're no longer needed, which I would consider a leak of sorts.
kvb
@kvb - You are correct but that has no bearing on this issue. That would create memory problems regardless of the algorithm used.
Andrew Hare
A: 

It won't leak any memory, it'll just generate an infinite sequence, but since sequences are IEnumerables you can enumerate them without memory concerns. The fact that the recursion happens inside the sequence generation function doesn't impact the safety of the recursion. Just mind that in debug mode tail call optimization may be disabled in order to allow full debugging, but in release there'll be no issue whatsoever.

emaster70
It depends on whether the function uses tail-recursion. For comparison, the equivalent C# code will throw a StackOverflowException after around 15000 integers:static IEnumerable<int> startFrom(int x) { yield return x; foreach (int y in startFrom(x + 1)) yield return y; }
Juliet
Addendum: after a quick test, it looks like the F# code *is* tail recursive. Thats good news :)
Juliet
+4  A: 

I'm at work now so I'm looking at slightly newer bits than Beta1, but on my box in Release mode and then looking at the compiled code with .Net Reflector, it appears that these two

let rec startFromA x =    
    seq {        
        yield x     
        yield! startFromA (x + 1)    
    }

let startFromB x =    
    let z = ref x
    seq {        
        while true do
            yield !z
            incr z
    }

generate almost identical MSIL code when compiled in 'Release' mode. And they run at about the same speed as this C# code:

public class CSharpExample
{
    public static IEnumerable<int> StartFrom(int x)
    {
        while (true)
        {
            yield return x;
            x++;
        }
    }
}

(e.g. I ran all three versions on my box and printed the millionth result, and each version took about 1.3s, +/- 1s). (I did not do any memory profiling; it's possible I'm missing something important.)

In short, I would not sweat too much thinking about issues like this unless you measure and see a problem.

EDIT

I realize I didn't really answer the question... I think the short answer is "no, it does not leak". (There is a special sense in which all 'infinite' IEnumerables (with a cached backing store) 'leak' (depending on how you define 'leak'), see

http://stackoverflow.com/questions/898988/avoiding-stack-overflow-with-f-infinite-sequences-of-sequences

for an interesting discussion of IEnumerable (aka 'seq') versus LazyList and how the consumer can eagerly consume LazyLists to 'forget' old results to prevent a certain kind of 'leak'.)

Brian
Thank you, again, Brian :) And kudos to the rest of the F# team as well :)
Juliet