重试monad和Zero构造



我正试图使用我从我们心爱的堆栈溢出中获得的重试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",但现在我遇到了一个编译时错误:

只有当生成器定义了"Zero"方法时,才能使用此构造。

有什么想法可以继续吗?

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")
}

看起来最简单的修复方法是在末尾返回一个值:

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

如果你看看计算表达式是如何去糖的,你的代码似乎被转换成了

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

这会导致在评估过程中引发异常。如果您返回一个值,则不会发生这种情况。

首先,你的x需要有一个类型注释。运行函数让编译器满意,因为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()函数的文档,我们会看到"在计算表达式中为if…Then表达式的空else分支调用"。这就解释了为什么编译器要求在计算表达式中将Zero存在。然后,如果我们在"if"上设置一个断点并观察它的执行,我们会看到文件移动返回单元,所以其他单元没有任何返回,因此它调用Zero。因此,这就解释了为什么在移动成功时会弹出Zero(然后在由于文件已被移动且不再存在而失败时重试)。

相关内容

  • 没有找到相关文章

最新更新