F# 部分延迟计算生成器
我正在尝试解决如何使用计算生成器来表示延迟的嵌套步骤集 到目前为止,我得到了以下信息:F# 部分延迟计算生成器,f#,monads,deferred,computation-expression,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:str
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
理想情况下,我希望能够检索树结构,但能够调用一些返回的函数,然后执行内部块
我意识到这意味着,如果您有两个嵌套的步骤,它们之间有一些“普通”代码,那么它需要能够读取步骤声明,然后调用它(如果有意义的话)
我知道,Delay
和Run
是在计算表达式的延迟执行中使用的东西,但我不确定它们在这里是否对我有帮助,因为不幸的是,它们对一切都进行了计算
我很可能错过了一些明显的、非常“实用”的东西,但我似乎无法让它做我想做的事情
澄清 我正在使用
id
进行演示,它们是谜题的一部分,我可以想象我将如何展示我想要的表达式的“可调用”部分。您写道:
理想情况下,我希望能够检索树结构,但能够调用一些返回的函数,然后执行内部块
这几乎是对“free monad”的完美描述,它基本上是OOP“解释器模式”的功能编程等价物。免费monad背后的基本思想是将命令式代码转换为两步过程。第一步建立AST,第二步执行AST。这样,您就可以在步骤1和步骤2之间完成一些事情,比如在不执行代码的情况下分析树结构。然后,当您准备好时,您可以运行“execute”函数,该函数将AST作为输入,并实际执行它所表示的步骤
我对免费单子没有足够的经验,无法就它们编写完整的教程,也无法用一步一步的特定免费单子解决方案直接回答您的问题。但我可以向您指出一些资源,这些资源可能有助于您理解它们背后的概念。首先,所需的Scott Wlaschin链接:
这是他“13种看海龟的方式”系列的最后一部分,在该系列中,他使用多种不同的设计风格构建了一个类似海龟图形的小徽标应用程序。在#13中,他使用了自由单子风格,从头开始构建,这样你就可以看到进入该风格的设计决策
第二,一组指向马克·希曼博客的链接。在过去的一两个月里,马克·希曼一直在写关于自由单子风格的帖子,尽管我直到他在《自由单子》杂志上发表了几篇文章才意识到这正是他所写的。一开始可能会让您感到困惑的是术语上的差异:Scott Wlaschin对两种可能的AST情况使用了术语“Stop”和“KeepGoing”(“这是命令列表的结尾”与“这之后还有更多命令”)。但这两个自由单子的传统名称是“纯”和“自由”。“纯”和“自由”的名字太抽象了,我更喜欢斯科特·沃斯钦的“停止”和“继续”的名字。但我提到这一点是为了当你在马克·希曼的帖子中看到“纯”和“自由”时,你会知道这和斯科特·沃拉钦的海龟例子是同一个概念
好了,解释完毕,下面是马克·希曼的帖子的链接:
正如我所说的,我对免费单子还不够熟悉,无法给你一个具体的答案。但希望这些链接能为您提供一些背景知识,帮助您实现所需目标。如另一个答案中所述,免费单子为思考此类问题提供了有用的理论框架-然而,我认为你不一定需要他们来回答你在这里提出的具体问题 首先,我必须将
Return
添加到您的计算生成器中,以使您的代码能够编译。由于您从未返回任何内容,我只是添加了一个重载,它使用单元
,相当于零
:
member this.Return( () ) = this.Zero()
现在,为了回答您的问题-我认为您需要修改您的判别并集,以允许延迟生成条目
的计算-您在域模型中确实有函数unit->unit
,但这还不足以延迟将生成新条目的计算。因此,我认为您需要扩展类型:
type Entry =
| Leaf of string * (unit -> unit)
| Node of string * Entry list * (unit -> unit)
| Delayed of (unit -> Entry)
当您评估条目
时,您现在需要处理延迟
案例-其中包含可能执行副作用的功能,例如打印“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
求值,直到它找到一个节点或叶(折叠所有其他延迟节点),然后执行与代码之前执行的相同操作
我想这回答了你的问题,但我在这里会小心一点。我认为计算表达式作为一种语法糖是有用的,但是如果你对它们的考虑超过了你所处理的问题的范围,那么它们是非常有害的