f# - Partially deferred computation builder -
i'm trying work out how use computation builder represent deferred, nested set of steps.
i've got following far:
type entry = | leaf of string * (unit -> unit) | node of string * entry list * (unit -> unit) type stepbuilder(desc:string) = member this.zero() = leaf(desc,id) member this.bind(v:string, f:unit->string) = node(f(), [leaf(v,id)], id) member this.bind(v:entry, f:unit->entry) = match f() | node(label,children,a) -> node(label, v :: children, a) | leaf(label,a) -> node(label, [v], a) let step desc = stepbuilder(desc) let = step "a" { do! step "b" { do! step "c" { do! step "c.1" { // todo: still evals goes; need find way defer // inner contents... printfn "test" } } } do! step "d" { printfn "d" } }
this produces desired structure:
a(b(c(c.1)), d)
my issue in building structure up, printfn
calls made.
ideally want able retrieve tree structure, able call returned function/s execute inner blocks.
i realise means if have 2 nested steps "normal" code between them, need able read step declarations, , invoke on (if makes sense?).
i know delay
, run
things used in deferred execution computation expressions, i'm not sure if me here, unfortunately evaluate everything.
i'm missing glaringly obvious , "functional-y" can't seem make want.
clarification
i'm using id
demonstration, they're part of puzzle, , imagine how might surface "invokable" parts of expression want.
as mentioned in other answer, free monads provide useful theoretical framework thinking kind of problems - however, think not need them answer specific question asking here.
first, had add return
computation builder make code compile. never return anything, added overload taking unit
equivalent zero
:
member this.return( () ) = this.zero()
now, answer question - think need modify discriminated union allow delaying of computations produce entry
- have functions unit -> unit
in domain model, that's not quite enough delay computation produce new entry. so, think need extend type:
type entry = | leaf of string * (unit -> unit) | node of string * entry list * (unit -> unit) | delayed of (unit -> entry)
when evaluating entry
, need handle delayed
case - contains function might perform side-effect such printing "test".
now can add delay
computation builder , implement missing case delayed
in bind
this:
member this.delay(f) = delayed(f) member this.bind(v:entry, f:unit->entry) = delayed(fun () -> let rec loop = function | delayed f -> loop (f()) | node(label,children,a) -> node(label, v :: children, a) | leaf(label,a) -> node(label, [v], a) loop (f()) )
essentially, bind
create new delayed computation that, when called, evaluates entry v
until finds node or leaf (collapsing other delayed nodes) , same thing code did before.
i think answers question - i'd bit careful here. think computation expressions useful syntactic sugar, harmful if think them more think domain of problem solving - in question, did not actual problem. if did, answer might different.
Comments
Post a Comment