我的理解是,工作流构建器的作用是首先“构建”表达式,然后执行它。因此,首先构建表达式,它应该能够在实际执行之前计算let!
语句的数量,对吧?那它应该能够注入一些监控进度的日志记录吗?那么是否可以重新设计async
构建器以自动报告进度并终止下面的printfn
冗余?
async {
let! a = doSomething1 ()
printfn "%d/%d" 1 4
let! b = doSomething2 a
printfn "%d/%d" 2 4
let! c = doSomething3 b
printfn "%d/%d" 3 4
let! d = doSomething4 c
printfn "%d/%d" 4 4
return d
}
对于循环,我想只是假设整个循环是一步。只有顶级表达式才算作步骤。
(请注意,如果有一种方法可以在不创建全新工作流程的情况下执行此操作,我想这也很好。)
注意我已经经历了a)制作一个“任务”迭代器的路径,它只是迭代任务(但是你失去了例如use
处理,所以它最终不合适),并且b)制作一个任务计数器,但总是必须手动播种和迭代,所以我希望有更好的东西。
答案 0 :(得分:2)
当你用标签 monads 标记问题时,我将从一个理论上的挑剔开始。您想要做的事实上不是 monad 。问题是monad需要某些定律(见the Haskell page on monads)。对于F#,这意味着以下两个片段应该表示相同的内容:
let computation1 =
async { let! x = m
return x }
let computation2 = m
对于您建议的扩展程序,情况并非如此,因为computation1
还有一个let!
而不是computation2
。现在,我不认为这实际上是一个问题 - 日志记录仍然有用(即使它可能会给出与您在某些情况下预期不同的结果)。
将此功能添加到F#async并不容易 - 问题是您需要定义自己的类型来替换(或包装)标准Async<'T>
。该类型需要存储步骤数。如果你可以在其他地方存储步骤数(例如一些可变计数器),那么你只需要重新定义async
的计算构建器。
以下是执行此类操作的最小示例 - 它只为每个"step"
打印let!
:
// A custom computation builder that redirects all operations to
// the standard 'async' builder, but prints "step" in the Bind method
type LogAsyncBuilder() =
member x.Bind(c1, f) = async {
let! arg = c1
printfn "step!"
return! f arg }
member x.Return(v) = async.Return(v)
member x.ReturnFrom(c) = async.ReturnFrom(c)
// An instance of our custom computation builder
let logAsync = LogAsyncBuilder()
// Example that prints 'step' 4 times (for every Bind - let!)
let doSomething n = logAsync {
return n + 10 }
logAsync {
let! a = doSomething 0
let! b = doSomething a
let! c = doSomething b
let! d = doSomething c
return d }
|> Async.RunSynchronously
答案 1 :(得分:1)
您可以使用元组('a, int, int)
来跟踪当前结果,步骤总数和到目前为止执行的数量。然后你可以编写一个函数来获取当前状态,然后编写下一个异步函数来执行,例如。
//create the initial state
let startCount steps = ((), 0, steps)
let withCount af (a, c, steps) = async {
let nc = c + 1
let! res = af a
do printfn "%d %d" nc steps
return (res, nc, steps)
}
withCount
接受一个返回下一个异步操作和当前状态的函数。它创建下一个工作流程,增加执行步骤的数量并在返回新状态之前打印状态。
然后您可以像:
一样使用它async {
let init = startCount 4
let! t = withCount doSomething init
let! t2 = withCount doSomething2 t
let! (r, _, _) = withCount doSomething3 t2
return r
}