Я пытаюсь использовать 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", но теперь получаю ошибку времени компиляции:

Эту конструкцию можно использовать только в том случае, если конструктор определяет метод «Ноль».

Есть идеи, как действовать?

3
Klark 17 Апр 2014 в 13:44

3 ответа

Лучший ответ

Похоже, самое простое решение - вернуть значение в конце:

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

Если вы посмотрите на как подслащивают выражения вычислений, ваш код, похоже, превратиться в

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

Это вызывает исключение во время оценки. Если вы вернете значение, этого не произойдет.

3
Lee 17 Апр 2014 в 10:49

Ли предположил, что вы можете использовать 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")
}
9
Tomas Petricek 17 Апр 2014 в 12:02

Во-первых, для вашей функции 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 (), мы видим «Вызывается для пустых ветвей else выражения if ... then в вычислительных выражениях». Это объясняет, почему компилятор требует, чтобы в выражении вычисления присутствовал ноль. Затем, если мы установим точку останова на if и посмотрим, как он выполняется, мы увидим, что перемещение файла возвращает единицу, поэтому else нечего возвращать, поэтому он вызывает Zero. Это объясняет, почему при успешном перемещении появляется всплывающее окно «Ноль» (а затем повторные попытки при сбое, поскольку файл был перемещен и больше не существует).

0
Fsharp Pete 17 Апр 2014 в 11:20