Wednesday, May 26, 2010

Computation Expressions with .NET Data Types

I’m still working on my proof-of-concept and it’s going well. One thing that’s been on my mind is the desire to use computation expressions in it somewhere. So I’ve been trying to make sure I understood computation expressions well enough to use them to good purpose.

To smooth the integration, the code will have to play nice with existing .NET components written in C#, etc. To this end, I am sometimes using .NET data structures – like List<> – in places where a pure F# implementation might use list, etc. So I decided to practice with some computation expressions that mingle in these other .NET data structures.

Here is a simple example. It splits a string into a tree using various characters and string.Split. It actually does its job using side-effects on the original tree node (Neil dodges rotten tomatoes thrown by the functional programming purists.) For example, splitting the string “a.b:c.d” by colon first and then by dot would result in this tree:



And here is the code. (As always, presented "as-is" and without warranty or implied fitness of any kind; use at your own risk.)

open System.Collections.Generic
open System.Text.RegularExpressions
 
 
// This is a simple tree node
// which uses List<>.
// Think of it as a proxy for
// some real-world problem.
type Tree = 
  { Text : string
    mutable Children : List<Tree> option }
  // Syntactic sugar.
  static member Make (s) = 
    { Text=s; Children=None }
 
 
// Splits the text in the node
// using string.split and
// uses it to create child nodes.
let splitter (c:char) =
  let a = [|c|]
  let f (t:Tree) =
    t.Children <- Some(new List<Tree>())
    Seq.iter 
      (fun s->t.Children.Value.Add(Tree.Make(s))) 
      (t.Text.Split(a,System.StringSplitOptions.None))
    t
  f
 
 
// This is almost like a collect,
// except it works through the 
// side-effects in the splitter function.
type TreeCollector () =
 
  member this.Bind ((lt:Tree),
                    (f:Tree->Tree)) =
    Seq.iter (fun t->(f t)|>ignore) lt.Children.Value
    lt
 
  member this.Return v = v
 
 
// Some tests.
 
let splitBar   = splitter '|'
let splitColon = splitter ':'
let splitDot   = splitter '.'
 
let treeCollector = TreeCollector()
 
// Build a tree by bar, colon, then dot.
let f0 t = treeCollector {
  let! x = splitBar t
  let! x = splitColon x
  let! x = splitDot x
  return x
}
 
// Build a tree by colon, bar, then dot.
let f1 t = treeCollector {
  let! x = splitColon t
  let! x = splitBar x
  let! x = splitDot x
  return x
}
 
 
// Simple test print.
let printo (t:Tree) =
  let rec f s (t:Tree) =
    printfn "%s %s" s t.Text 
    if t.Children.IsSome then
      Seq.iter (f (s+"  ")) t.Children.Value
  f "" t
 
 
let t0 = Tree.Make("a0.a1:b0.b1|c0.c1:d0.d1")
let t1 = Tree.Make(t0.Text)
 
f0 t0 |> ignore
f1 t1 |> ignore
 
printo t0
printfn ""
printo t1
 
printfn "Done."

No comments: