重试monad和Zero构造

时间:2014-04-17 09:44:28

标签: f# monads computation-expression

我正在尝试使用我从心爱的堆栈溢出中获取的Retry Monad:

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

我想用它来使我的文件复制代码更健壮:

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

现在我注意到它有时会因为" Zero"而失败。例外。我试图删除member x.Zero() = failwith "Zero"但现在我得到编译时错误:

  

此构造只能在构建器定义“零”的情况下使用。方法

任何想法如何进行?

3 个答案:

答案 0 :(得分:9)

Lee建议你可以在计算结束时使用return ()否则会抛出,因为他们会调用Zero成员。这是一个很好的技巧 - 但您实际上可以将它直接集成到计算构建器中。

当您的计算结束而不返回时,将使用Zero成员。您可以将其更改为与return ()执行相同的操作:

type RetryBuilder(max, sleep : TimeSpan) = 
  member x.Return(a) = ...
  member x.Zero() = x.Return( () )

然后您可以编写原始代码,然后您将获得单位结果:

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

答案 1 :(得分:3)

看起来最简单的解决方法是在最后返回一个值:

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

如果查看how computation expressions are de-sugared,您的代码似乎已转换为

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

这会导致在评估期间抛出异常。如果你返回一个值,这将不会发生。

答案 2 :(得分:0)

首先需要为x.Run函数添加一个类型注释,以使编译器感到满意,因为File.Move需要单位并返回单位。像这样:

open System
open System.Threading
type RetryBuilder(max, sleep : TimeSpan) = 
      member x.Return(a) = a
      member x.Delay(f) = f
      member x.Zero() = failwith "Zero"
      member x.Run(f : unit -> unit) =
        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

然后看看Zero()函数上的documentation,我们看到“调用if ... then计算表达式中的表达式的其他分支。”这就解释了为什么编译器要求您在计算表达式中存在Zero。然后,如果我们在'if'上设置一个断点并观察它执行,我们会看到文件move返回unit,所以else没有任何东西可以返回,因此它调用Zero。这就解释了为什么在移动成功时弹出Zero的原因(然后由于文件已被移动而不再存在而重试失败)。