f# - 部分延迟计算构建器

标签 f# monads deferred computation-expression

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

到目前为止,我得到了以下内容:

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"
    }
}

这会产生所需的结构:

A(B(C(c.1)), D)

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

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

我意识到这意味着如果你有两个嵌套的步骤,它们之间有一些“正常”代码,它需要能够读取步骤声明,然后调用它(如果这有意义的话?)。

我知道 DelayRun 是用于计算表达式的延迟执行的东西,但我不确定它们是否对我有帮助,因为不幸的是评估一切。

我很可能遗漏了一些非常明显且非常“实用”的东西,但我似乎无法让它做我想做的事情。


澄清

我正在使用 id 进行演示,它们是拼图的一部分,我想像我可以如何呈现我想要的表达式的“可调用”部分。

最佳答案

正如另一个答案中提到的,free monads 为思考这类问题提供了一个有用的理论框架 - 但是,我认为您不一定需要它们来获得特定问题的答案你在这里问。

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

member this.Return( () ) = this.Zero()

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

type Entry =
  | Leaf of string * (unit -> unit)
  | Node of string * Entry list * (unit -> unit)
  | Delayed of (unit -> Entry)

当您评估 Entry 时,您现在需要处理 Delayed 案例 - 它包含一个可能执行副作用的函数,例如打印“TEST”。

现在您可以将 Delay 添加到您的计算构建器中,并在 Bind 中实现 Delayed 的缺失案例,如下所示:

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()) )

本质上,Bind 将创建一个新的延迟计算,当调用该计算时,它会评估条目 v 直到找到一个节点或叶子(折叠所有其他延迟节点)然后做和你的代码之前做的一样的事情。

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

关于f# - 部分延迟计算构建器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45684743/

相关文章:

datetime - f# 中自定义时钟的更好解决方案

haskell - 混合解析器字符(词法分析器?)与解析器字符串

javascript - 将回调附加到充当一次触发的全局事件的 promise 的概念是否有一个名称? (如 $(document).ready())

jquery - 为什么我的jquery延迟处理错误和jsonp数据不起作用?

javascript - canjs 模板模型尚未准备好。数据绑定(bind)

f# - 有人可以解释这些 F# 类型推理的奇怪之处吗?

c# - 如何创建具有参数和私有(private)类型返回类型的快速调用委托(delegate),加速 DynamicInvoke

c# - F#中Dictionary的缩写(别名)

haskell - 创建我自己的状态单子(monad)

haskell - 使用 Foldl 和 Reader monad 递归地遍历 AST,无需样板