首页 > 解决方案 > F# Computation Expression to build state and defer execution

问题描述

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.

标签: f#computation-expression

解决方案


从 OP 问题中找出精确的解决方案有点困难。相反,我将发布一些代码,OP 也许可以根据需要进行调整。

我定义了 Result 和 ResultGenerator

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

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

生成器产生一个值和一个直接值和延迟值的列表,直接值是上面的字符串列表,但与它们混合的是延迟值。我喜欢混合返回,以便保留顺序。

请注意,这是有时称为Statemonad 的版本。

除了像bindBuilders 这样的类 CE 组件之外,我还直接和延迟创建了两个函数。

direct用于创建直接值和delayed延迟值(带函数)

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

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

为了提高可读性,我定义了延迟trace函数:

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

let tracef fmt = kprintf trace fmt

从示例生成器:

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

取得了以下结果:

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

(延迟将在执行时打印跟踪)。

希望这可以就如何解决实际问题提供一些想法。

完整来源:

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

推荐阅读