Search code examples
f#sequenceenumerationyield

F# Cannot enumerate sequence generated by yield when using GetEnumerator


The following example is based on a snippet that produces functions that allow enumerating sequence values one by one.

Here printAreEqual () gives true, print2 () gives 12345678910, but print1 () gives 0000000000.

Why cannot the function returned by enumerate return the values of the sequence generated using yield?


open System.Linq

let enumerate (xs: seq<_>)  = 
    use en = xs.GetEnumerator()
    fun () ->
        en.MoveNext() |> ignore
        en.Current

let s1 = seq { for i in 1 .. 10 do yield i }
let s2 = seq { 1 .. 10 }

let f1 = s1 |> enumerate
let f2 = s2 |> enumerate

let printAreEqual () = Enumerable.SequenceEqual (s1, s2) |> printf "%b" // true
let print1 () = for i in 1 .. 10 do f1() |> printf "%i" // 0000000000
let print2 () = for i in 1 .. 10 do f2() |> printf "%i" // 12345678910

Solution

  • The use en = ... in the enumerate function is effectively doing this:

    let enumerate (xs: seq<_>) = 
        let en = xs.GetEnumerator()
        let f =
            fun () ->
                en.MoveNext() |> ignore
                en.Current
        en.Dispose()
        f
    

    You're always disposing of the enumerator before you start using it, so the behaviour is probably undefined in this situation and it doesn't matter why you get different results for two sequences with different implementations.

    Fine-grained control of sequence enumeration is always tricky and it's hard to make helper functions for because of the mutable state.