2016-04-10 18 views
17

Başarılı ve başarısız olabilecek bir dizi görev (yan efektlerle) oluşturmak için F # ve Chessie kullanıyorum.Ya monad ("demiryolu yönelimli programlama") kullanırken geri almalarla nasıl başa çıkılır

Bir şey başarısız olursa, kalan görevleri yürütmeyi ve daha önce başarılı olanları geri almayı durdurmak istiyorum.

Maalesef bir kez 'başarısızlık' yolunu vurduğumda, başarılı görevlerin sonuçlarını almanın bir yolu yok, böylece onları geri alabilirim.

Bu senaryoyla ilgilenen işlevsel bir programlama modeli var mı?

Örnek:

kişi Açıklamalarda belirttiği gibi, bu çözmek için kullanılabilecek seçenekler bir çift vardır
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

Sonuçlar yine de geri dönmek için yeterli olur mu? Muhtemelen gerçek geri alma işlemlerini bir araya getiren bir sonuç türü için giderim, bu nedenle herhangi bir adımda değer bir başarı/başarısızlık (şimdi olduğu gibi) ve bir geri alma işlevidir() ->() 'dir. –

+0

Kötü için bunu işlevsel bir soru olarak sordun. Prolog'u kullanarak veya [inferencing] (https://en.wikipedia.org/wiki/Inference_engine) anında akla geliyor. F # ile çıkarımı uygulayabilirsiniz ve harika çalışıyor. –

+0

Ganesh: Hmm bu ilginç bir fikir. Etrafta bir oyun oynayacağım ve nasıl çalıştığını göreceğim! – Oenotria

cevap

18

.

Tek yön compensating transactions'u kullanmaktır.

Bu yaklaşımda, Success kasası "geri alma" işlevlerinin bir listesini içerir. Geri alınamayan her adım bu listeye bir işlev ekler. Herhangi bir adım başarısız olduğunda, listedeki her bir geri alma işlevi yürütülür (ters sırada).

Elbette bunu yapmak için daha karmaşık yollar vardır (ör. Çökmeler durumunda geri alma işlevlerini kalıcı olarak saklamak, veya this kind of thing). İşte

bu yaklaşımı gösteren bazı kod:

/// 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..]) 
    *) 

her sonuçları geri alınamaz, ikinci bir seçenek, tüm her adımda geri dönüşü olmayan şeyleri yapmaya fakat geciktirmek için değil tüm adımlar tamamlanana kadar geri dönüşü olmayan bitler.

Bu yaklaşımda, Success kasası "execute" işlevlerinin bir listesini içerir. Başarılı olan her adım bu listeye bir işlev ekler. En sonunda, tüm işlevler listesi yürütülür.

olumsuz

(! Ayrıca monadik çok olanlar zincirlemek rağmen) işlenen bir kez, tüm fonksiyonlar çalıştırılır olmasıdır Bu temelde tercüman desen çok kaba bir versiyonu.

İşte bu yaklaşımı gösteren bazı kod:

/// 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 ",[]) 
    *) 

Sen fikir, umarım. Eminim başka yaklaşımlar da vardır - bunlar açık ve basit olan ikisi. :)

+3

Bu hızlı, güzel cevaptı. "Desen" dediğine inanamıyorum, kendini asimile ettin. –

+2

direnç dayanıklıdır! – Grundoon