Here's the solution I've got so far. It creates an IEnumerable instead of a Seq.
Are there any built-in F# functions that would simplify this?
Or make a Seq instead?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  let mynull : 'a when 'a:not struct = (# "ldnull" : 'a #)  // From Stephan

  /// Create an IEnumerable out of two functions:
  /// GetNext returns the next value (and is called first).
  /// KeepGoing tests that value (e.g. for null or Nil) & returns true if is valid.
  type EnumGivenGetNext<'a when 'a:not struct> (getNext:unit->'a (*nullable*), keepGoing:'a->bool) =
    let mutable _value :'a = mynull
    member it.Current0 =
      if _value = mynull then failwith "EnumGivenGetNext- called Current when no value"
      _value
    interface Coll.Generic.IEnumerator<'a> with
      member it.Current with get() = it.Current0
      member it.Dispose() = ()
    interface Coll.IEnumerator with
      member it.Current with get() = box(it.Current0)
      member it.MoveNext() =
        _value <- getNext()
        (_value <> mynull)
      member it.Reset() = failwith "EnumGivenGetNext- can't reset"
    member it.MoveNext() = (it:>Coll.IEnumerator).MoveNext()
    member it.Current = it.Current0
  
  let NotNull x = (x <> mynull) //null)
  
  /// Create an IEnumerable given a .NET function that returns null when exhausted.
  let EnumGivenFuncTillNull<'a when 'a:not struct> (f:unit->'a) =
    new EnumGivenGetNext<'a>(f, NotNull)
By on 4/3/2008 12:56 PM ()

Thanks Tomas -- you posted while I was posting!
Here is what I ended up with, to satisfy the compiler:

1
2
3
4
5
6
7
8
9
10
  /// HACK: Emitting IL code "ldnull"
  let mynull : 'a when 'a:not struct = (# "ldnull" : 'a #)  // From Stephan
  
  /// Usage: "SeqGivenFuncTillNull<string>(sr.ReadLine)"
  let SeqGivenFuncTillNull<'a when 'a:not struct> (f:unit->'a) :seq<'a> =
    seq{
        let v = ref (f())
        while (!v) <> mynull do
          yield !v
          do v := f() }
By on 4/3/2008 12:57 PM ()

Hi,
I personally don't like using nulls in F#, so unless you have some very good reason (like you want to call this function from C#), I'd prefer to use cleaner F# solution using "option" type. It may be slightly slower, but it makes the code much cleaner and also safer and more readable:

1
2
3
4
5
6
7
 
let SeqGivenFuncTillNull (f:unit->'a option) :seq<'a> =
    seq{
        let v = ref (f())
        while (!v) <> None do
          yield Option.get (!v)
          do v := f() }

.. and I believe it will be easier to use it with other F# libraries, because in the standard F# modules it is more usual to return 'a option when the function may exit returning no value.

By on 4/5/2008 5:32 AM ()

Tomas, I agree about not using Nulls. If you look closely at my original post, the reason I was doing so is that I was calling a .NET built-in function, System.IO.StringReader.ReadLine. The function SeqGivenFuncTillNull wraps the .NET function, returning a seq -- protecting the rest of my F# code from that nasty .NET library :)

Thanks for posting what you did -- it is good to show good practices, so that someone new reading this thread doesn't think I was suggesting a good style for general F# use!

Also, you showed me in another thread how to write the function cleanly:

1
2
3
4
5
6
7
8
  /// Usage: "SeqGivenFuncTillNull<string>(sr.ReadLine)"
  /// From Tomas
  let SeqGivenFuncTillNull<'a when 'a:not struct> (f:unit->'a) :seq<'a> =
    seq{
        let v = ref (f()) // Call function first time, storing return value in a ref cell.
        while box (!v) <> null do // Test the stored value.
          yield !v    // Return the stored value as part of sequence.
          do v := f() } // Call function once per loop, storing return value in that ref cell.

If one is forced to deal with nulls from .NET, this works nicely!

By on 4/6/2008 1:32 AM ()

Another technique to isolate nullness is to build up a library of extension members and use those systematically, e.g.

1
2
3
4
5
6
7
8
9
10
11
 

#light

type System.IO.TextReader with 
    member x.TryReadLine() = 
        match x.ReadLine() with 
        | null -> None 
        | line -> Some(line)

By on 4/6/2008 5:22 AM ()

I like that approach. In this situation, since ReadLine is logically a sequence, I would define a "to_seq" member for IO.TextReader, analogous to converting an array or other construct to a sequence. Since could be sequence of chars or strings, called it "to_string_seq". Building on the function I defined above:

1
2
3
4
5
6
7
8
9
10
 
/// Extend IO.TextReader.
type System.IO.TextReader with
  member it.to_string_seq = SeqGivenFuncTillNull<string>(it.ReadLine)
...
// Usage:
let s = "first line\nsecond line\nthird line\n"
let sr = new IO.StringReader(s)
for line in sr.to_string_seq do   // Life is good :)
  ...
By on 4/6/2008 9:02 PM ()

Hi,
I would use sequence expressions with a mutable variable and a while loop inside:

1
2
3
4
5
 
seq { let v = ref (sr.ReadLine())
      while (!v) <> null do
        yield v
        do v := sr.ReadLine() };;

You could do it in other ways too - e.g. using sequence expressions recursively, but that would be less efficient or using some function from Seq module, but that's probably less elegant.

By on 4/3/2008 12:56 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper