部分延迟计算构建器

Cli*_*int 3 monads f# computation-expression deferred

我正在尝试研究如何使用计算构建器来表示延迟的嵌套步骤集.

到目前为止我有以下内容:

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() with
        | Node(label,children,a) -> Node(label, v :: children, a)
        | Leaf(label,a) -> Node(label, [v], a)


let step desc = StepBuilder(desc)

let a = step "a" {
    do! step "b" {
        do! step "c" {
            do! step "c.1" {
                // todo: this still evals as it goes; need to find a way to defer
                // the inner contents...
                printfn "TEST"
            }
        }
    }
    do! step "d" {
        printfn "d"
    }
}
Run Code Online (Sandbox Code Playgroud)

这产生了所需的结构:

A(B(C(c.1)), D)
Run Code Online (Sandbox Code Playgroud)

我的问题是,在构建结构时,会进行printfn调用.

理想情况下,我想要的是能够检索树结构,但能够调用一些返回的函数,然后执行内部块.

我意识到这意味着如果你有两个嵌套的步骤,它们之间有一些"普通"代码,它需要能够读取步骤声明,然后调用它(如果这有意义吗?).

我知道,DelayRun有些事情在延迟执行用于计算表达式,但我不知道,如果他们帮助我在这里,因为他们不幸的是,一切评价.

我很可能错过了一些明显而且非常"功能性"的东西,但我似乎无法让它做到我想要的.


澄清

我正在使用它id进行演示,它们是这个难题的一部分,我想象我如何表达我想要的表达的"可调用的"部分.

Tom*_*cek 6

正如在另一个答案中所提到的,免费monad为思考这类问题提供了一个有用的理论框架 - 但是,我认为你并不一定需要它们来回答你在这里提出的具体问题.

首先,我必须添加Return到您的计算构建器以使您的代码编译.因为你永远不会返回任何东西,我只是添加了一个过载unit,相当于Zero:

member this.Return( () ) = this.Zero()
Run Code Online (Sandbox Code Playgroud)

现在,回答你的问题 - 我认为你需要修改你的区别联合以允许延迟产生的计算Entry- 你确实unit -> unit在域模型中有函数,但这还不足以推迟产生新条目的计算.所以,我认为你需要扩展类型:

type Entry =
  | Leaf of string * (unit -> unit)
  | Node of string * Entry list * (unit -> unit)
  | Delayed of (unit -> Entry)
Run Code Online (Sandbox Code Playgroud)

在评估时Entry,您现在需要处理Delayed案例 - 其中包含可能执行副作用的函数,例如打印"TEST".

现在,您可以添加Delay到您的计算建设者和也实现了丢失的情况下,DelayedBind这样的:

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()) )
Run Code Online (Sandbox Code Playgroud)

本质上,Bind将创建一个新的延迟计算,在调用时,它会评估条目,v直到它找到一个节点或一个叶子(折叠所有其他延迟的节点),然后执行与您之前的代码相同的操作.

我认为这回答了你的问题 - 但我在这里会有点小心.我认为计算表达式作为一种语法糖是有用的,但是如果你考虑它们而不是考虑你实际解决的问题领域,它们是非常有害的 - 在这个问题上,你没有多说你的实际问题.如果你这样做,答案可能会非常不同.