Search code examples
f#monadscomputation-expression

Retry monad and Zero construct


I am trying to use the Retry Monad I took from our beloved stack overflow:

type RetryBuilder(max, sleep : TimeSpan) = 
      member x.Return(a) = a
      member x.Delay(f) = f
      member x.Zero() = failwith "Zero"
      member x.Run(f) =
        let rec loop(n) = 
            if n = 0 then failwith "Failed"
            else 
                try f() 
                with ex -> 
                    sprintf "Call failed with %s. Retrying." ex.Message |> printfn "%s"
                    Thread.Sleep(sleep); 
                    loop(n-1)
        loop max

I would like to use it to make my file copy code a bit more robust:

let retry = RetryBuilder(3, TimeSpan.FromSeconds(1.))
retry {
    System.IO.File.Move("a", "b")
}

Now I noticed that it sometimes fails with "Zero" exception. I tried to remove the member x.Zero() = failwith "Zero" but now I get a compile time error:

This construct can be only used if builder defines a 'Zero' method.

Any ideas how to proceed?


Solution

  • It looks like the simplest fix is to return a value at the end:

    retry {
        System.IO.File.Move("a", "b")
        return ()
    }
    

    If you look at how computation expressions are de-sugared your code seems to be converted into

    retry.Run(retry.Delay(fun () -> System.IO.File.Move("a", "b"); retry.Zero()))
    

    this causes the exception to be thrown during the evaluation. If you return a value this will not happen.