如何创建支持单步执行等功能的 F# 工作流程?
我想创建一个构建器来构建表达式,该表达式在每个步骤后返回类似于延续的内容。
像这样:
module TwoSteps =
let x = stepwise {
let! y = "foo"
printfn "got: %A" y
let! z = y + "bar"
printfn "got: %A" z
return z
}
printfn "two steps"
let a = x()
printfn "something inbetween"
let b = a()
“let a”行返回包含稍后要计算的其余表达式的内容。
对每个步骤数使用单独的类型执行此操作很简单,但当然不是特别有用:
type Stepwise() =
let bnd (v: 'a) rest = fun () -> rest v
let rtn v = fun () -> Some v
member x.Bind(v, rest) =
bnd v rest
member x.Return v = rtn v
let stepwise = Stepwise()
module TwoSteps =
let x = stepwise {
let! y = "foo"
printfn "got: %A" y
let! z = y + "bar"
printfn "got: %A" z
return z
}
printfn "two steps"
let a = x()
printfn "something inbetween"
let b = a()
module ThreeSteps =
let x = stepwise {
let! y = "foo"
printfn "got: %A" y
let! z = y + "bar"
printfn "got: %A" z
let! z' = z + "third"
printfn "got: %A" z'
return z
}
printfn "three steps"
let a = x()
printfn "something inbetween"
let b = a()
printfn "something inbetween"
let c = b()
结果就是我正在寻找的:
two steps
got: "foo"
something inbetween
got: "foobar"
three steps
got: "foo"
something inbetween
got: "foobar"
something inbetween
got: "foobarthird"
但我无法弄清楚这种情况的一般情况是什么。
我想要的是能够将事件输入到此工作流程中,这样您就可以编写如下内容:
let someHandler = Stepwise<someMergedEventStream>() {
let! touchLocation = swallowEverythingUntilYouGetATouch()
startSomeSound()
let! nextTouchLocation = swallowEverythingUntilYouGetATouch()
stopSomeSound()
}
让事件触发工作流程中的下一步。 (特别是,我想在 iPhone 上的 MonoTouch - F# 中玩弄这种东西。传递 objc 选择器让我发疯。)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您的实现的问题在于,每次调用 Bind 都会返回“unit -> 'a”,因此您将针对不同数量的步骤获得不同类型的结果(一般来说,这是 monad/ 的可疑定义)计算表达式)。
正确的解决方案应该是使用其他类型,它可以表示具有任意步骤数的计算。您还需要区分两种类型的步骤 - 某些步骤仅评估计算的下一步,而某些步骤则返回结果(通过
return
关键字)。我将使用类型seq
。这是一个惰性序列,因此读取下一个元素将评估下一步的计算。该序列将包含None
值,但最后一个值除外,该值将为Some(value)
,表示使用return
返回的结果。您的实现中的另一个可疑之处是
Bind
成员的非标准类型。事实上,您的绑定采用一个值作为第一个参数,这意味着您的代码看起来更简单(您可以编写let!a = 1
),但是您无法编写逐步计算。您可能希望能够编写:我上面描述的类型也将允许您编写此内容。获得类型后,您只需在实现中遵循
Bind
和Return
的类型签名,您就会得到以下结果:现在,让我们看看如何使用该类型:
如果您想逐步运行计算,您可以简单地迭代返回的序列,例如使用 F#
for
关键字。下面还打印了该步骤的索引:希望这有帮助!
PS:我发现这个问题很有趣!您介意我将我的答案添加到我的博客的博客文章中吗(http://tomasp.net/blog)?谢谢!
the problem with your implementation is that it returns "unit -> 'a" for each call to Bind, so you'll get a different type of result for different number of steps (in general, this is a suspicious definition of monad/computation expression).
A correct solution should be to use some other type, which can represent a computation with arbitrary number of steps. You'll also need to distinguish between two types of steps - some steps just evaluate next step of the computation and some steps return a result (via the
return
keyword). I'll use a typeseq<option<'a>>
. This is a lazy sequence, so reading the next element will evaluate the next step of the computation. The sequence will containNone
values with the exception of the last value, which will beSome(value)
, representing the result returned usingreturn
.Another suspicious thing in your implementation is a non-standard type of
Bind
member. The fact that your bind takes a value as the first parameter means that your code looks a bit simpler (you can writelet! a = 1
) however, you cannot compose stepwise computation. You may want to be able to write:The type I described above will allow you to write this as well. Once you have the type, you just need to follow the type signature of
Bind
andReturn
in the implementation and you'll get this:Now, let's look at using the type:
If you want to run the computation step-by-step, you can simply iterate over the returned sequence, for example using the F#
for
keyword. The following also prints the index of the step:Hope this helps!
PS: I found this problem very interesting! Would you mind if I addapted my answer into a blog post for my blog (http://tomasp.net/blog)? Thanks!
Monad 和计算构建器让我很困惑,但我已经改编了我在 之前的 SO 帖子。也许一些零碎的东西可以派上用场。
下面的代码包含一个操作队列和一个表单,其中 Click 事件侦听操作队列中的下一个可用操作。下面的代码是连续 4 个操作的示例。在 FSI 中执行它并开始单击表单。
您可以单击该表单 4 次,然后再单击则不会发生任何情况。
现在执行以下代码,表单将再响应两次:
Monads and computation builders confuse the hell out of me, but I've adapted something I've made in an earlier SO post. Maybe some bits and pieces can be of use.
The code below contains an action queue, and a form where the Click event listens to the next action available in the action queue. The code below is an example with 4 actions in succession. Execute it in FSI and start clicking the form.
You can click the form 4 times and then nothing happens with further clicks.
Now execute the following code, and the form will respond two more times: