具有F#和异步的Monadic Retry逻辑?

Hen*_*rik 1 f# asynchronous

我找到了这个片段:

http://fssnip.net/8o

但我不仅正在使用可重复的功能,而且还使用异步功能,我想知道我是如何正确地使用这种类型的.我有一小块retryAsyncmonad,我想用它作为异步计算的替代品,但它包含重试逻辑,我想知道我是如何组合它们的?

type AsyncRetryBuilder(retries) =
  member x.Return a = a               // Enable 'return'
  member x.ReturnFrom a = x.Run a
  member x.Delay f = f                // Gets wrapped body and returns it (as it is)
                                       // so that the body is passed to 'Run'
  member x.Bind expr f = async {
    let! tmp = expr
    return tmp
    }
  member x.Zero = failwith "Zero"
  member x.Run (f : unit -> Async<_>) : _ =
    let rec loop = function
      | 0, Some(ex) -> raise ex
      | n, _        -> 
        try 
          async { let! v = f()
                  return v }
        with ex -> loop (n-1, Some(ex))
    loop(retries, None)

let asyncRetry = AsyncRetryBuilder(4)
Run Code Online (Sandbox Code Playgroud)

消费代码是这样的:

module Queue =
  let desc (nm : NamespaceManager) name = asyncRetry {
    let! exists = Async.FromBeginEnd(name, nm.BeginQueueExists, nm.EndQueueExists)
    let beginCreate = nm.BeginCreateQueue : string * AsyncCallback * obj -> IAsyncResult
    return! if exists then Async.FromBeginEnd(name, nm.BeginGetQueue, nm.EndGetQueue)
            else Async.FromBeginEnd(name, beginCreate, nm.EndCreateQueue)
    }

  let recv (client : MessageReceiver) timeout =
    let bRecv = client.BeginReceive : TimeSpan * AsyncCallback * obj -> IAsyncResult
    asyncRetry { 
      let! res = Async.FromBeginEnd(timeout, bRecv, client.EndReceive)
      return res }
Run Code Online (Sandbox Code Playgroud)

错误是:

这个表达式应该有类型,Async<'a>但这里有类型'b - >Async<'c>

Tom*_*cek 6

您的Bind操作行为类似于正常Bind操作async,因此您的代码主要是重新实现(或包装)async.然而,你Return没有正确的类型(应该是'T -> Async<'T>)和你Delay也较正常不同Delayasync.一般来说,你应该从BindReturn- 开始使用Run有点棘手,因为Run它用于包装整个foo { .. }块,所以它不会给你通常很好的可组合性.

F#规范以及免费的第12章,从现实世界的函数式编程都显示常用的类型来实现这些操作时,应遵循,所以我不会重复在这里.

您的方法的主要问题是您尝试仅重试计算Run,但您所指的重试构建器尝试重试每个使用的单独操作let!.您的方法可能已足够,但如果是这种情况,您可以实现一个尝试运行正常Async<'T>和重试的函数:

 let RetryRun count (work:Async<'T>) = async { 
   try 
     // Try to run the work
     return! work
   with e ->
     // Retry if the count is larger than 0, otherwise fail
     if count > 0 then return! RetryRun (count - 1) work
     else return raise e }
Run Code Online (Sandbox Code Playgroud)

如果你真的想要实现一个计算构建器,它将隐式地尝试重试每一个异步操作,那么你可以编写类似下面的东西(它只是一个草图,但它应该指向正确的方向):

// We're working with normal Async<'T> and 
// attempt to retry it until it succeeds, so 
// the computation has type Async<'T>
type RetryAsyncBuilder() =
  member x.ReturnFrom(comp) = comp // Just return the computation
  member x.Return(v) = async { return v } // Return value inside async
  member x.Delay(f) = async { return! f() } // Wrap function inside async
  member x.Bind(work, f) =
    async { 
      try 
        // Try to call the input workflow
        let! v = work
        // If it succeeds, try to do the rest of the work
        return! f v
      with e ->
        // In case of exception, call Bind to try again
        return! x.Bind(work, f) }
Run Code Online (Sandbox Code Playgroud)