用于构建状态和延迟执行的 F# 计算表达式

F# Computation Expression to build state and defer execution

我正在寻找可以表达以下内容的计算表达式:

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
}

并能够从 x 列表中获取:

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

没有 printfn 被调用,但能够稍后执行它(如果这有意义的话)。

它不需要与 do! 关键字一起使用,它可以是 yieldreturn,只要它起作用就可以。

换句话说,我希望能够在计算快车中收集一些状态,并排队稍后可以执行的工作(printfns)。

我已经尝试了一些方法,但不确定是否可行。

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

我定义了Result和ResultGenerator

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

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

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

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

除了 class CE 组件,如 bind 和 Builders,我还直接创建了两个函数和延迟函数。

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"])

(Delayed会在执行时打印trace)

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

完整来源:

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