Монадическая логика повторов с F # и асинхронность? - PullRequest
1 голос
/ 10 февраля 2012

Я нашел этот фрагмент:

http://fssnip.net/8o

Но я работаю не только с повторяемыми функциями, но и с асинхронными, и мне было интересно, как это сделатьвведите правильно.У меня есть крошечный кусочек retryAsync монады, который я хотел бы использовать в качестве замены для асинхронных вычислений, но он содержит логику повторных попыток, и мне интересно, как их объединить?

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)

Потреблениекод выглядит так:

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 }

Ошибка:

Ожидается, что это выражение будет иметь тип Async<'a>, но здесь имеет тип 'b -> Async<'c>

1 Ответ

6 голосов
/ 10 февраля 2012

Ваша Bind операция ведет себя как обычная Bind операция с async, поэтому ваш код в основном является повторной реализацией (или оболочкой) над async. Однако ваш Return не имеет правильного типа (он должен быть 'T -> Async<'T>), и ваш Delay также отличается от обычного Delay из async. В общем, вы должны начинать с Bind и Return - использование 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 }

Если вы действительно хотите реализовать построитель вычислений, который будет неявно пытаться повторить каждую асинхронную операцию, то вы можете написать что-то вроде следующего (это просто набросок, но он должен указывать вам правильное направление):

// 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) }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...