の人がコメントで指摘したように、これを解決するために使用することができるオプションがいくつかあります。
一方向は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つです。 :)
結果はとにかく戻ってくるのに十分でしょうか?私はおそらく、実際のロールバック操作を蓄積する結果型に行くだろう。だから、どんなステップでも値は成功/失敗(今のところ)とロールバック関数 '() - >()'のタプルです。 –
あなたは機能的な質問としてこれを頼んだ。 Prologを使うか、[inferencecing](https://en.wikipedia.org/wiki/Inference_engine)がすぐに思い浮かびます。あなたはF#で推論を実装することができ、それは素晴らしい動作します。 –
ガネーシュ:うーん、面白い考えです。私は周りの遊びをして、それがどのように動作するか見るでしょう! – Oenotria