3

我正在尝试使用从我们心爱的堆栈溢出中获取的 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")
}

现在我注意到它有时会因“零”异常而失败。我试图删除member x.Zero() = failwith "Zero"但现在我得到一个编译时错误:

仅当构建器定义“零”方法时才能使用此构造。

任何想法如何进行?

4

3 回答 3

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")
}
于 2014-04-17T12:02:10.137 回答
3

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

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

如果您查看计算表达式是如何脱糖的,您的代码似乎被转换为

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

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

于 2014-04-17T10:49:38.857 回答
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() 函数的文档,我们会看到“Called for empty else branch of if...then 计算表达式中的表达式”。这就解释了为什么编译器要求您在计算表达式中存在零。然后,如果我们在“if”上设置一个断点并观察它的执行,我们会看到文件移动返回单元,因此 else 没有任何内容可返回,因此它调用零。这就解释了为什么在移动成功时会弹出零(然后在失败时重试,因为文件已被移动且不再存在)。

于 2014-04-17T11:12:09.277 回答