Dato che le persone hanno fatto notare nei commenti, ci sono un paio di opzioni che possono essere utilizzate per risolvere questo problema.
Un modo è utilizzare compensating transactions.
In questo approccio, la custodia Success
contiene un elenco di funzioni di "annullamento". Ogni passaggio che può essere annullato aggiunge una funzione a questo elenco. Quando un passo fallisce, ogni funzione di annullamento nella lista viene eseguita (nell'ordine inverso).
Ci sono modi più sofisticati per farlo naturalmente (ad es. Memorizzare le funzioni di annullamento in modo permanente in caso di arresti anomali, o this kind of thing).
Ecco po 'di codice che illustra questo approccio:
/// 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..])
*)
Se i risultati di ogni non possono essere annullate, una seconda opzione non è fare le cose irreversibili in ogni passaggio a tutti, ma di ritardare il bit irreversibili finché tutti i passaggi non sono OK.
In questo approccio, la custodia Success
contiene un elenco di funzioni "esegui". Ogni passo che riesce aggiunge una funzione a questa lista. Alla fine, viene eseguito l'intero elenco di funzioni.
Il rovescio della medaglia è che una volta commesso, tutte le funzioni vengono eseguite (anche se si potrebbe anche concatenare quelli monadico troppo!)
Questo è fondamentalmente una versione molto rozza del modello interprete.
Ecco po 'di codice che illustra questo approccio:
/// 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 ",[])
*)
Si ottiene l'idea, spero. Sono sicuro che ci sono anche altri approcci - questi sono due che sono ovvi e semplici. :)
I risultati sarebbero sufficienti per tornare indietro con lo stesso? Probabilmente opterei per un tipo di risultato che accumuli le operazioni di rollback effettive, quindi in qualsiasi momento il valore è una tupla di success/failure (come ora) e una funzione di rollback '() ->()'. –
Per quanto riguarda il problema, l'hai chiesto come una domanda funzionale. Mi viene subito in mente l'utilizzo di Prolog o [inferencing] (https://en.wikipedia.org/wiki/Inference_engine). È possibile implementare l'inferenza in F # e funziona benissimo. –
Ganesh: Hmm è un'idea interessante. Avrò un gioco e vedrò come funziona! – Oenotria