3

我的理解是,工作流构建器所做的是它首先“构建”表达式,然后执行它。所以假设它首先构建表达式,它应该能够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) 制作一个任务计数器,但总是有手动播种和迭代,所以我希望有更好的东西。

4

2 回答 2

2

当您使用标签monads标记问题时,我将从理论上的挑剔开始。你想要做的实际上不是一个monad。问题是 monads 需要某些规则(请参阅monads 上的 Haskell 页面)。对于 F#,这意味着以下两个代码段应该是同一个意思:

let computation1 = 
  async { let! x = m
          return x }
let computation2 = m

对于您建议的扩展名,情况并非如此,因为computation1let!computation2. 现在,我不认为这实际上是一个问题 - 日志记录可能仍然有用(即使在某些情况下它可能会给出与您预期不同的结果)。

将此功能添加到 F# async 并不容易 - 问题是您需要定义自己的类型来替换(或包装) standard 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
于 2013-08-17T23:01:47.243 回答
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
}
于 2013-08-17T18:50:09.487 回答