Повторите вычисление выражения или другой конструкции в F # - PullRequest
3 голосов
/ 10 мая 2011

Я хочу иметь возможность написать вычислительное выражение на F #, которое сможет повторить операцию, если она выдает исключение.Прямо сейчас мой код выглядит следующим образом:

let x = retry (fun() -> GetResourceX())
let y = retry (fun() -> GetResourceY())
let z = retry (fun() -> DoThis(x, y))
etc. (this is obviously an astract representation of the actual code)

Мне нужно иметь возможность повторять каждую из функций определенное количество раз, которое я определил здесь.

Я думал, что здесь может помочь вычислительное выражение, но я не понимаю, как оно могло бы помочь мне убрать явное завершение каждой правой стороны к Retryable <'T>

Я мог быПосмотрите на выражение для вычисления, которое выглядит примерно так:

let! x = Retryable( fun() -> GetResourceX())
etc.

Я понимаю, что монады, грубо говоря, являются типами-обертками, но я надеялся обойти это.Я знаю, что могу перегрузить оператор и иметь очень лаконичный синтаксис для преобразования операции в Retryable <'T>, но для меня это просто делает повторение / перенос более лаконичным;это все еще там.Я мог бы обернуть каждую функцию в Retryable <'T>, но еще раз, я не вижу значения сверх того, что делается в верхней части поста (вызов повторения для каждой операции. По крайней мере, это очень явно).

Может быть, здесь вычисления - неправильная абстракция, я не уверен.Любые идеи о том, что можно сделать здесь?

Ответы [ 3 ]

6 голосов
/ 10 мая 2011

Вычислительные выражения имеют несколько расширений (в дополнение к стандартным монадическим функциям), которые дают вам хороший способ сделать это.

Как вы сказали, монады по сути являются обертками (создающими, например, Retryable<'T>), которые имеют некоторое дополнительное поведение.Тем не менее, выражение вычисления F # также может определять Run член, который автоматически разворачивает значение, поэтому результат retry { return 1 } может иметь только тип int.

Вот пример (построитель ниже):

let rnd = new System.Random()
// The right-hand side evaluates to 'int' and automatically
// retries the specified number of times
let n = retry { 
  let n = rnd.Next(10)
  printfn "got %d" n
  if n < 5 then failwith "!"  // Throw exception in some cases
  else return n }

// Your original examples would look like this:
let x = retry { return GetResourceX() }
let y = retry { return GetResourceY() }
let z = retry { return DoThis(x, y) }

Вот определение retry построителя.На самом деле это не монада, потому что она не определяет let! (когда вы используете вычисления, созданные с использованием retry в другом блоке retry, она просто повторяет внутренний X-раз, а внешний Y-разпо мере необходимости).

type RetryBuilder(max) = 
  member x.Return(a) = a               // Enable 'return'
  member x.Delay(f) = f                // Gets wrapped body and returns it (as it is)
                                       // so that the body is passed to 'Run'
  member x.Zero() = failwith "Zero"    // Support if .. then 
  member x.Run(f) =                    // Gets function created by 'Delay'
    let rec loop(n) = 
      if n = 0 then failwith "Failed"  // Number of retries exceeded
      else try f() with _ -> loop(n-1)
    loop max

let retry = RetryBuilder(4)
3 голосов
/ 10 мая 2011

Может работать простая функция.

let rec retry times fn = 
    if times > 1 then
        try
            fn()
        with 
        | _ -> retry (times - 1) fn
    else
        fn()

Тестовый код.

let rnd = System.Random()

let GetResourceX() =
    if rnd.Next 40 > 1 then
        "x greater than 1"
    else
        failwith "x never greater than 1" 

let GetResourceY() =
    if rnd.Next 40 > 1 then
        "y greater than 1"
    else
        failwith "y never greater than 1" 

let DoThis(x, y) =
    if rnd.Next 40 > 1 then
        x + y
    else
        failwith "DoThis fails" 


let x = retry 3 (fun() -> GetResourceX())
let y = retry 4 (fun() -> GetResourceY())
let z = retry 1 (fun() -> DoThis(x, y))
0 голосов
/ 12 мая 2011

Вот первая попытка сделать это в одном выражении вычисления.Но будьте осторожны, это только первая попытка; Я не тщательно его проверил .Кроме того, это немного уродливо, если заново установить количество попыток в выражении вычисления.Я думаю, что синтаксис можно было бы немного улучшить в этой базовой структуре.

let rand = System.Random()

let tryIt tag =
  printfn "Trying: %s" tag
  match rand.Next(2)>rand.Next(2) with
  | true -> failwith tag
  | _ -> printfn "Success: %s" tag

type Tries = Tries of int

type Retry (tries) =

  let rec tryLoop n f =
    match n<=0 with
    | true -> 
      printfn "Epic fail."
      false
    | _ -> 
      try f()
      with | _ -> tryLoop (n-1) f 

  member this.Bind (_:unit,f) = tryLoop tries f 
  member this.Bind (Tries(t):Tries,f) = tryLoop t f
  member this.Return (_) = true

let result = Retry(1) {
  do! Tries 8
  do! tryIt "A"
  do! Tries 5
  do! tryIt "B"
  do! tryIt "C" // Implied: do! Tries 1
  do! Tries 2
  do! tryIt "D" 
  do! Tries 2
  do! tryIt "E"
}


printfn "Your breakpoint here."

ps Но мне больше нравятся версии как Томаса, так и Градбота.Я просто хотел посмотреть, как может выглядеть этот тип решения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...