【问题标题】:Change async workflow builder to count steps?更改异步工作流构建器以计算步骤?
【发布时间】:2013-08-17 18:02:46
【问题描述】:

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

【问题讨论】:

    标签: f# monads


    【解决方案1】:

    当您使用标签 monads 标记问题时,我将从理论上的挑剔开始。你想要做的实际上并不是一个monad。问题是 monads 需要特定的法则(见the Haskell page on monads)。对于 F#,这意味着以下两个 sn-ps 应该是同一个意思:

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

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

    将此功能添加到 F# async 并不容易 - 问题是您需要定义自己的类型来替换(或包装)标准 Async<'T>。类型需要存储步数。如果您可以将步数存储在其他地方(例如一些可变计数器),那么您只需为async 重新定义计算构建器。

    这是一个做类似事情的最小示例 - 它只是为每个 let! 打印 "step"

    // 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
    

    【讨论】:

    • 是的,写完这个问题后我突然想到它真的与异步没有任何特别的关系——我真正想要的只是一个计步器;将它与异步步骤混为一谈只是一个领域细节。然而,我的问题的症结更多地是关于我的建造者能否确定有 N let! (/do!/for/whatever) 我的工作流程中的顶级语句,而无需我明确指定,并且它当前正在执行步骤 i of ?这可能吗?
    • (最好不包括use作为顶级结构;这样use中的任何let!s都将被独立计算)
    【解决方案2】:

    您可以使用元组 ('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
    }
    

    【讨论】:

    • 是的,这基本上是我已经提出的最佳选择。它也适用于 C#。我希望得到一些东西,实际上只是作为一个借口,我可以使用一些东西来告诉老板我们需要 F#,在那里我可以使用工作流或一些特定于 F# 的功能来无需考虑完全
    猜你喜欢
    • 1970-01-01
    • 2017-02-01
    • 2019-12-17
    • 2018-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-11
    相关资源
    最近更新 更多