3

例外がスローされた場合に操作を再試行できる計算式を 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> にラップすることもできますが、繰り返しになりますが、投稿の上部で行われたことを実行することに価値はありません (各操作で再試行を呼び出します。少なくとも非常に明示的です)。

計算式はここでは間違った抽象化かもしれませんが、よくわかりません。ここで何ができるかについてのアイデアはありますか?

4

3 に答える 3

6

計算式には (標準のモナド機能に加えて) いくつかの拡張機能があり、これを行うのに便利です。

Retryable<'T>あなたが言ったように、モナドは本質的にいくつかの追加の動作を持つラッパー(たとえばを作成する)です。ただし、F# の計算式ではRun、値を自動的にアンラップするメンバーも定義できるため、 の結果はretry { return 1 }type だけを持つことができます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!使用して作成された計算を使用する場合、必要に応じて内側のブロックを X 回、外側のブロックを Y 回再試行します)。retryretry

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)
于 2011-05-09T20:30:00.333 に答える
3

単純な関数が機能する可能性があります。

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))
于 2011-05-09T20:32:28.627 に答える
0

これは、単一の計算式でこれを行う最初の試みです。ただし、これは最初の試行にすぎないことに注意してください。私はそれを完全にテストしていません。また、計算式内で試行回数を再設定するときは少し醜いです。この基本的なフレームワーク内で、構文をかなりクリーンアップできると思います。

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しかし、私はTomasのバージョンとgradbotのバージョンの両方が好きです。このタイプのソリューションがどのように見えるかを見たかっただけです。

于 2011-05-12T14:38:07.540 に答える