Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enumerator and disposing in F#

I am trying to draw lessons on the following behaviour from an example I simplified :

let groupedEnum  (input: 'a seq) =
   using (input.GetEnumerator()) (fun en ->
   Seq.unfold(fun _ -> 
                  if en.MoveNext() then 
                     Some(en.Current, ())
                  else None) ()
   )


//WORKS    
let c = groupedEnum    ("11111122334569999"   |>  List.ofSeq ) |>  List.ofSeq 

//BOOM !!  System.NullReferenceException
let c = groupedEnum    ("11111122334569999"                  ) |>  List.ofSeq
  • Is the enumerator "en" disposed of independently of it being captured ? (I guess it is but is there anything to say / materials to read on this behaviour beside this msdn doc on ressources)

  • Why is it working if the sequence is transformed to a list first ?

Edit : this is just a toy example to illustrate a behaviour, not to be followed. There are very few good reasons to manipulate enumerators directly.

like image 446
nicolas Avatar asked Feb 18 '23 09:02

nicolas


1 Answers

The using function disposes the enumerator as soon as the lambda function returns. However, the lambda function creates a lazy sequence using Seq.unfold and the lazy sequence accesses the enumerator after the sequence is returned from groupedEnum.

You could either fully evaluate the whole sequence inside using (by adding List.ofSeq there) or you need to call Dispose when the end of the generated sequence is reached:

let groupedEnum  (input: 'a seq) =
   let en = input.GetEnumerator()
   Seq.unfold(fun _ -> 
       if en.MoveNext() then 
           Some(en.Current, ())
       else 
           en.Dispose()
           None)

Exception handling becomes quite difficult in this case, but I guess that one way to do it would be to wrap the body in try .. with and call Dispose if an exception happens (and then return None).

If you use sequence expressions instead, then the meaning of use changes and it automatically disposes the enumerator after the end of sequence is reached (not when the lazy sequence is returned). So using sequence expressions might be a better choice because the hard work is done for you:

let groupedEnum  (input: 'a seq) = seq {
   use en = input.GetEnumerator()
   let rec loop () = seq {
      if en.MoveNext() then 
         yield en.Current
         yield! loop () }
   yield! loop () }

EDIT And why does it work in your first example? The enumerator returned by F# list type simply ignores Dispose and continues working, while if you call Dispose on an enumerator returned by a string, the enumerator cannot be used again. (This is arguably a bit odd behaviour of the F# list type.)

like image 152
Tomas Petricek Avatar answered Feb 22 '23 01:02

Tomas Petricek