Don Syme has been telling me again and again to make my experiences with F# more widely available by putting it on my blog; here is my first attempt at "being a better boy".

I am a big fan of F# and I have recently posted an on hubFS a piece of source code for reading from a SQL stream by using the IEnumerable class. However, the code there had two problems:

  1. You could not reset the connection. That means that even if you output the value of the IEnumerable (for debug purposes, let's say) you started to digest the results of the read and never got them back unless you re-created the IEnumerable.
  2. You could not make sure that the reader is properly closed unless all results are digested (in which case you do close the reader).

We have both looked into the issue and rewrote the actual implementation of IEnumerable.unfold to remedy the issues. Here is the new code that allows to specifcy not only the compute function but also the initialisation and closing function.

module IEnumerable = begin
   open System.Collections
   open System.Collections.Generic

   ///The new IEnumerator with a specific open, compute and close function.
   module IEnumerator = begin
      
let end_of_stream() = 
         raise(
new System.ArgumentException("An attempt was made to move past the end of an IEnumerator")) 
      
let unfolds openf compute closef = 
         
let curr = ref None in
         
let state = ref (openf()) in 
         
let getCurr() = match !curr with None -> end_of_stream() | Some x -> x in 
         
{    new IEnumerator<'b>
               
with get_Current() = getCurr()
               
interface IEnumerator 
               
with get_Current() = box (getCurr())
               and MoveNext() = 
               
let s = !state in
               
match compute s with 
               | None
-> (closef s; curr := None; false
               | Some(r,s)
-> curr := Some r; state := s; true
               
and Reset() = (closef !state; state := openf(); curr := None)
               
interface System.IDisposable
               
with Dispose() = () 
      } 
   end

   ///The new ways to create an IEnumerable from a IEnumerator
   let mk_IEnumerable f = 
   {    
new IEnumerable<'b> with GetEnumerator() = f()
         
interface IEnumerable with GetEnumerator() = (f() :> IEnumerator) }

   ///The new way to create an IEnumerable via the unfolds function.
   let unfolds openf compute closef = mk_IEnumerable (fun () -> IEnumerator.unfolds openf compute closef) end

Now it is clear how to give the SQL reader via IEnumerables back the functionality that was missing,  isn't it?