2016-04-10 7 views
17

F#とChessieを使用して、成功または失敗する一連のタスク(副作用あり)を作成しています。いずれかのモナド(「鉄道指向プログラミング」)を使用するときのロールバックの処理方法

何かが失敗した場合、残りのタスクの実行を停止し、既に成功したタスクをロールバックします。

残念ながら、私が「失敗」パスを辿ると、成功したタスクの結果を取得する方法がなくなり、ロールバックすることができます。

このシナリオを扱う機能プログラミング "パターン"はありますか?

例:

let refuel = 
    async { 
    printfn "1 executed" 
    // Fill missile with fuel 
    return Result<string,string>.Succeed "1" 
    } |> AR 

let enterLaunchCodes = 
    async { 
    printfn "2 executed" 
    // 
    return Result<string,string>.FailWith "2" 
    } |> AR 

let fireMissile = 
    async { 
    printfn "3 executed" 
    return Result<string,string>.Succeed "3" 
    } |> AR 

let launchSequence = 
    asyncTrial { 
    let! a = refuel 
    let! b = enterLaunchCodes 
    let! c = fireMissile 
    return a,b,c 
    } 

let result = launchSequence 
    |> Chessie.ErrorHandling.AsyncExtensions.Async.ofAsyncResult 
    |> Async.RunSynchronously 

// Result is a failure... how do I know the results of the successful operations here so I can roll them back? 

printfn "Result: %A" result 
+4

結果はとにかく戻ってくるのに十分でしょうか?私はおそらく、実際のロールバック操作を蓄積する結果型に行くだろう。だから、どんなステップでも値は成功/失敗(今のところ)とロールバック関数 '() - >()'のタプルです。 –

+0

あなたは機能的な質問としてこれを頼んだ。 Prologを使うか、[inferencecing](https://en.wikipedia.org/wiki/Inference_engine)がすぐに思い浮かびます。あなたはF#で推論を実装することができ、それは素晴らしい動作します。 –

+0

ガネーシュ:うーん、面白い考えです。私は周りの遊びをして、それがどのように動作するか見るでしょう! – Oenotria

答えて

18

の人がコメントで指摘したように、これを解決するために使用することができるオプションがいくつかあります。

一方向はcompensating transactionsです。

この方法では、Successケースには「元に戻す」機能のリストが含まれています。取り消すことができるすべてのステップは、このリストに関数を追加します。 ステップが失敗すると、リスト内の各元に戻す機能が実行されます(逆の順序で実行されます)。

もちろんこれを行うためのもっと洗練された方法があります(たとえば、クラッシュの場合には、取り消し機能を永続的に保存する、 またはthis kind of thing)。ここで

は、このアプローチを示していますいくつかのコードです:各の結果がを元に戻すことができない場合

/// ROP design with compensating transactions  
module RopWithUndo = 

    type Undo = unit -> unit 

    type Result<'success> = 
     | Success of 'success * Undo list 
     | Failure of string 

    let bind f x = 
     match x with 
     | Failure e -> Failure e 
     | Success (s1,undoList1) -> 
      match f s1 with 
      | Failure e -> 
       // undo everything in reverse order 
       undoList1 |> List.rev |> List.iter (fun undo -> undo()) 
       // return the error 
       Failure e 
      | Success (s2,undoList2) -> 
       // concatenate the undo lists 
       Success (s2, undoList1 @ undoList2) 

/// Example 
module LaunchWithUndo = 

    open RopWithUndo 

    let undo_refuel() = 
     printfn "undoing refuel" 

    let refuel ok = 
     if ok then 
      printfn "doing refuel" 
      Success ("refuel", [undo_refuel]) 
     else 
      Failure "refuel failed" 

    let undo_enterLaunchCodes() = 
     printfn "undoing enterLaunchCodes" 

    let enterLaunchCodes ok refuelInfo = 
     if ok then 
      printfn "doing enterLaunchCodes" 
      Success ("enterLaunchCodes", [undo_enterLaunchCodes]) 
     else 
      Failure "enterLaunchCodes failed" 

    let fireMissile ok launchCodesInfo = 
     if ok then 
      printfn "doing fireMissile " 
      Success ("fireMissile ", []) 
     else 
      Failure "fireMissile failed" 

    // test with failure at refuel 
    refuel false 
    |> bind (enterLaunchCodes true) 
    |> bind (fireMissile true) 
    (* 
    val it : Result<string> = Failure "refuel failed" 
    *) 

    // test with failure at enterLaunchCodes 
    refuel true 
    |> bind (enterLaunchCodes false) 
    |> bind (fireMissile true) 
    (* 
    doing refuel 
    undoing refuel 
    val it : Result<string> = Failure "enterLaunchCodes failed" 
    *) 

    // test with failure at fireMissile 
    refuel true 
    |> bind (enterLaunchCodes true) 
    |> bind (fireMissile false) 
    (* 
    doing refuel 
    doing enterLaunchCodes 
    undoing enterLaunchCodes 
    undoing refuel 
    val it : Result<string> = Failure "fireMissile failed" 
    *) 

    // test with no failure 
    refuel true 
    |> bind (enterLaunchCodes true) 
    |> bind (fireMissile true) 
    (* 
    doing refuel 
    doing enterLaunchCodes 
    doing fireMissile 
    val it : Result<string> = 
     Success ("fireMissile ",[..functions..]) 
    *) 

、2番目のオプションは、すべてで各ステップで不可逆的なことを行う が、遅延することではなく、全てのステップがOKになるまで不可逆ビットにします。

この場合、Successのケースには「実行」機能のリストが含まれています。成功するたびにこのリストに関数が追加されます。 最後に、関数のリスト全体が実行されます。

欠点は

(!あなたもmonadicallyあまりにもそれらをチェーン可能性が)約束したら、すべての機能が実行されていることである。これは、基本的には通訳パターンの非常に粗製のバージョンです。ここで

は、このアプローチを示していますいくつかのコードです:

/// ROP design with delayed executions 
module RopWithExec = 

    type Execute = unit -> unit 

    type Result<'success> = 
     | Success of 'success * Execute list 
     | Failure of string 

    let bind f x = 
     match x with 
     | Failure e -> Failure e 
     | Success (s1,execList1) -> 
      match f s1 with 
      | Failure e -> 
       // return the error 
       Failure e 
      | Success (s2,execList2) -> 
       // concatenate the exec lists 
       Success (s2, execList1 @ execList2) 

    let execute x = 
     match x with 
     | Failure e -> 
      Failure e 
     | Success (s,execList) -> 
      execList |> List.iter (fun exec -> exec()) 
      Success (s,[]) 

/// Example 
module LaunchWithExec = 

    open RopWithExec 

    let exec_refuel() = 
     printfn "refuel" 

    let refuel ok = 
     if ok then 
      printfn "checking if refuelling can be done" 
      Success ("refuel", [exec_refuel]) 
     else 
      Failure "refuel failed" 

    let exec_enterLaunchCodes() = 
     printfn "entering launch codes" 

    let enterLaunchCodes ok refuelInfo = 
     if ok then 
      printfn "checking if launch codes can be entered" 
      Success ("enterLaunchCodes", [exec_enterLaunchCodes]) 
     else 
      Failure "enterLaunchCodes failed" 

    let exec_fireMissile() = 
     printfn "firing missile" 

    let fireMissile ok launchCodesInfo = 
     if ok then 
      printfn "checking if missile can be fired" 
      Success ("fireMissile ", [exec_fireMissile]) 
     else 
      Failure "fireMissile failed" 

    // test with failure at refuel 
    refuel false 
    |> bind (enterLaunchCodes true) 
    |> bind (fireMissile true) 
    |> execute 
    (* 
    val it : Result<string> = Failure "refuel failed" 
    *) 

    // test with failure at enterLaunchCodes 
    refuel true 
    |> bind (enterLaunchCodes false) 
    |> bind (fireMissile true) 
    |> execute 
    (* 
    checking if refuelling can be done 
    val it : Result<string> = Failure "enterLaunchCodes failed" 
    *) 

    // test with failure at fireMissile 
    refuel true 
    |> bind (enterLaunchCodes true) 
    |> bind (fireMissile false) 
    |> execute 
    (* 
    checking if refuelling can be done 
    checking if launch codes can be entered 
    val it : Result<string> = Failure "fireMissile failed" 
    *) 

    // test with no failure 
    refuel true 
    |> bind (enterLaunchCodes true) 
    |> bind (fireMissile true) 
    |> execute 
    (* 
    checking if refuelling can be done 
    checking if launch codes can be entered 
    checking if missile can be fired 
    refuel 
    entering launch codes 
    firing missile 
    val it : Result<string> = Success ("fireMissile ",[]) 
    *) 

あなたがアイデアを得る、私は願っています。私は他のアプローチもあると確信しています - これらは明らかで単純な2つです。 :)

+3

それは速く、素敵な答えでした。私は、あなたがパターンに同化したとあなたが信じることはできません。 –

+2

抵抗は無駄です! – Grundoon

関連する問題