2

I am looking to build a computation expression where I can express the following:

let x = someComputationExpression {
    do! "Message 1"
    printfn "something 1"
    do! "Message 2"
    printfn "something 2"
    do! "Message 3"
    printfn "something 3"
    let lastValue = 4
    do! "Message 4"
    // need to reference values across `do!`
    printfn "something %s" lastValue
}

and be able to take from x a list:

[| "Message 1"
   "Message 2"
   "Message 3"
   "Message 4" |]

without printfn ever getting called, but with the ability to later execute it (if that makes sense).

It doesn't need to be with the do! keyword, it could be yield or return, whatever is required for it to work.

To put it another way, I want to be able to collect some state in a computation express, and queue up work (the printfns) that can be executed later.

I have tried a few things, but am not sure it's possible.

Stuart
  • 5,358
  • 19
  • 28
  • I can think of several possible solutions but how would you like to return the delayed actions? Should `let` in your example also be delayed? Can a delayed action return a value, in that case the following expressions might depend on it and the CE can't complete until the delayed action is executed – Just another metaprogrammer Dec 23 '18 at 11:07
  • Thanks for the response. The `let`s could be delayed. The delayed work cannot return a value. – Stuart Dec 23 '18 at 11:20

1 Answers1

2

It's a bit hard to figure out a precise solution from the OP question. Instead I am going to post some code that the OP perhaps can adjust to the needs.

I define Result and ResultGenerator

type Result =
  | Direct  of string
  | Delayed of (unit -> unit)

type ResultGenerator<'T> = G of (Result list -> 'T*Result list )

The generator produces a value and a list of direct and delayed values, the direct values are the string list above but intermingled with them are the delayed values. I like returning intermingled so that the ordering is preserved.

Note this is a version of what is sometimes called a State monad.

Apart from the class CE components like bind and Builders I created two functions direct and delayed.

direct is used to create a direct value and delayed a delayed one (takes a function)

let direct v : ResultGenerator<_> =
  G <| fun rs ->
    (), Direct v::rs

let delayed d : ResultGenerator<_> =
  G <| fun rs ->
    (), Delayed d::rs

To improve the readability I defined delayed trace functions:

let trace m : ResultGenerator<_> =
  G <| fun rs ->
    (), Delayed (fun () -> printfn "%s" m)::rs

let tracef fmt = kprintf trace fmt

From an example generator:

let test =
  builder {
    do! direct "Hello"
    do! tracef "A trace:%s" "!"
    do! direct "There"
    return 123
  }

The following result was achieved:

(123, [Direct "Hello"; Delayed <fun:trace@37-1>; Direct "There"])

(Delayed will print the trace when executed).

Hope this can give some ideas on how to attack the actual problem.

Full source:

open FStharp.Core.Printf

type Result =
  | Direct  of string
  | Delayed of (unit -> unit)

type ResultGenerator<'T> = G of (Result list -> 'T*Result list )

let value v : ResultGenerator<_> =
  G <| fun rs ->
    v,  rs

let bind (G t) uf : ResultGenerator<_> =
  G <| fun rs ->
    let tv, trs = t rs
    let (G u) = uf tv
    u trs

let combine (G t) (G u) : ResultGenerator<_> =
  G <| fun rs ->
    let _, trs = t rs
    u trs

let direct v : ResultGenerator<_> =
  G <| fun rs ->
    (), Direct v::rs

let delayed d : ResultGenerator<_> =
  G <| fun rs ->
    (), Delayed d::rs

let trace m : ResultGenerator<_> =
  G <| fun rs ->
    (), Delayed (fun () -> printfn "%s" m)::rs

let tracef fmt = kprintf trace fmt

type Builder() =
  class
    member x.Bind       (t, uf) = bind t uf
    member x.Combine    (t, u)  = combine t u
    member x.Return     v       = value v
    member x.ReturnFrom t       = t : ResultGenerator<_>
  end

let run (G t) =
  let v, rs = t []
  v, List.rev rs

let builder = Builder ()

let test =
  builder {
    do! direct "Hello"
    do! tracef "A trace:%s" "!"
    do! direct "There"
    return 123
  }

[<EntryPoint>]
let main argv =
  run test |> printfn "%A"
  0