Wednesday, May 19, 2010

F# -- excessively lazy sequences and stateful visitors

Doing something a little bit complicated here: the functional equivalent of a visitor pattern, where I want to apply a few functions to a long and potentially expensive to traverse sequence of items (so ruling out a series of iterations over the visited nodes) --

let ToSeq x =
seq {yield x}
let visitor (node:Node) =
// pattern match on the node type to do visitor stuff
// use mutable state to record context
let internal apply (visitors : seq<Node -> unit>) (node : Node) =
visitors |>
Seq.iter (fun v -> v node)
let internal Visit (visitors : seq<Node -> unit>) =
BuildNodeSequence
|> Seq.iter (apply visitors)
...
Visit (ToSeq visitor)
...
view raw gistfile1.fs hosted with ❤ by GitHub

BuildNodeSequence itself returns a nested sequence expression of the data to visit, and state is well, expected to be handled by mutable out-of-band means.

This works nicely, but is impure -- so let's try to fix that by having the visitor function update itself with the transient state we need to carry between (there will still be side-effects for output). The code becomes

type Fix<'T> = delegate of 'T -> Fix<'T>
let internal invoke (node : Node) (visitor:Fix<Node>) =
visitor.Invoke(node)
let internal apply (visitors : seq<Fix<Node>>) (node : Node) =
visitors |>
Seq.map (invoke node)
let internal Visit (visitors : seq<Fix<Node>>) =
BuildNodeSequence
|> Seq.fold apply visitors
|> ignore
...
Visit (ToSeq visitor)
...
view raw gistfile1.fs hosted with ❤ by GitHub

where the Fix delegate type is a way of reifying an otherwise infinite type Node ->'a where 'a is also Node ->'a through

let StatefulVisitor state node =
// pattern match on the node type to do visitor stuff
// use input and returned values to record context
newState
let rec stateful state = new Fix<Node> (
fun (node:Node) ->
let next = StatefulVisitor state node
stateful next)
let visitor (node:Node) =
stateful initialState
view raw gistfile1.fs hosted with ❤ by GitHub

and have it build

Well, I run this and...

Nothing happens -- or at least apply gets called for every node to visit, but invoke never did; until I forced the evaluation of the visitors sequence by

let internal apply (visitors : seq<Fix<Node>>) (node : Node) =
Console.WriteLine(Seq.count visitors) // putting Console.WriteLine(visitors.Count) just echoed the delegate name, which gave me the clue as to a fix...
visitors |>
Seq.map (invoke node)
view raw gistfile1.fs hosted with ❤ by GitHub

So, what I finally ended up with is

let internal apply (visitors : list<Fix<Node>>) (node : Node) =
visitors |>
List.map (invoke node)
let internal Visit (visitors : list<Fix<Node>>) =
BuildNodeSequence
|> Seq.fold apply visitors
|> ignore
...
Visit [visitor]
...
view raw gistfile1.fs hosted with ❤ by GitHub

where the inner iterable is evaluated eagerly, rather than being left to lazy IEnumerable expansion; and this then works.

No comments :