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) | |
... |
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) | |
... |
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 |
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) |
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] | |
... |
where the inner iterable is evaluated eagerly, rather than being left to lazy IEnumerable
expansion; and this then works.