2015-04-24 5 views
7

Ich bin vertraut mit der Tatsache, dass in F # gibt es keine gleichwertige "Return" -Schlüsselwort.Wie implementiert man die "frühe Rückkehr" -Logik in F #

Wir sind jedoch kürzlich auf ein Problem gestoßen, bei dem wir einen Workflow benötigt haben, der aus vielen Schritten besteht, wobei jeder Schritt ein gutes oder ein schlechtes Ergebnis liefern kann. Wenn in einem der Schritte ein schlechtes Ergebnis gefunden wird, wollten wir den Workflow beenden und vorzeitig beenden!

Wir haben es geschafft, indem wir effektiv nach einem Fehler in jedem Schritt (d. H. Funktion) gesucht haben, aber ich glaube nicht, dass dies der richtige Weg ist - es ist ineffizient und wir verlassen nicht früh.

Eine Beispielfunktion im Workflow ist wie folgt:

let StepB stepAResult someParameters = 
    match stepAResult with 
    | Good(validResourceData) -> 
     // Do current step processing 
     // Return current step result 
    | Error(error) -> error |> Result.Error 

Der Workflow selbst wie folgt lautet:

let Workflow someParameters = 
    let stepAResult = StepA someParameters 
    let stepBResult = StepB stepAResult someParameters 
    let stepCResult = StepC stepBResult someParameters 
    let stepDResult = StepD stepCResult someParameters 
    stepDResult 

So jede Beispielfunktion in dem Ergebnis der vorherigen Funktion übernehmen würde und Führen Sie den aktuellen Schritt nur aus, wenn kein Fehler aufgetreten ist!

Das Problem dabei ist, dass, wenn StepA mit einem Fehler fehlschlägt, jeder zweite Schritt noch aufgerufen wird.

Gibt es eine "funktionale" Möglichkeit, "früh" zurückzukommen, anstatt jede Funktion im Workflow aufzurufen, wo wir jedes Mal nach einem Fehler suchen müssen?

+5

Bitte lesen Sie diese: http://fsharpforfunandprofit.com/posts/recipe-part2/ –

Antwort

9

Sie schreiben Ihre Funktionen unter der Annahme, dass alles so gut gelaufen ist wie Sie es getan haben. Dann wickeln Sie den glücklichen Fall aus und fahren mit dem glücklichen Fall fort.

Und am Ende können Sie den Builder verwenden, um die Syntax hübsch zu machen.

type Result<'TSuccess, 'TError> = 
    | Success of 'TSuccess 
    | Error of 'TError 

type ResultBuilder() = 
    member this.Bind(v, f) = 
     match v with 
     | Success v -> f v 
     | Error e -> Error e 

    member this.Return value = Success value 

let result = ResultBuilder() 

let bla<'a> = result { 
    let! successOne = Success 1 
    let! successTwo = Success 2 
    let! failure = Error "after this, the computation exited" 
    failwith "Boom, won't occurr" 
    return successOne + successTwo } 
+0

Dieser Vorschlag ist mehr oder weniger mit dem wir gegangen sind, aber es bedeutet, dass Sie nicht "früh zurückkehren". Jede Funktion im Workflow muss effektiv nach einem Fehlerfall "prüfen". Danke für die Klärung! – bstack

3

Dies ist, was Computation Expressions sind.

Berechnungsausdrücke liefern einen schönen syntaktischen Zucker für die so genannte monadische Komposition, wobei der vorhergehende resultierende Wert automatisch überprüft wird, bevor der nächste Schritt ausgeführt wird.

Ich habe kürzlich über dieses Konzept geredet - es steht auf youtube unter https://www.youtube.com/watch?v=gNNTuN6wWVc; und @scottwlaschin hat eine detaillierte einführung dazu unter http://fsharpforfunandprofit.com/series/computation-expressions.html

Ping mich auf Twitter, wenn Sie mehr Hilfe wollen!

+0

Aber bedeutet das, dass wir bei jedem Schritt überprüfen? Wenn Schritt A mit einem Fehler fehlschlägt, möchte ich beenden, ich möchte dies nicht in Schritt B, C und D überprüfen? – bstack

+1

Was meinst du mit "Ausgang"? * Wenn das bedeutet, dass Sie keine der nachfolgenden Funktionen im Workflow ausführen, passiert genau das im Workflow. * Wenn es bedeutet, dass Sie keine weitere Überprüfung des Ergebniswerts tun, kann es tatsächlich nicht in einer allgemeinen Art und Weise getan werden. Der Begriff "Ausgang" ist ein Kater von einem sehr imperativen Stil des Denkens - Sie Es wäre besser, wenn Sie den Begriff eines Wertes verwenden, der aus der Verkettung mehrerer Funktionen besteht ... –

+1

Sie müssen den Prüfcode auch nicht selbst schreiben - Sie stellen nur die Funktionen zur Verfügung, die am Workflow teilnehmen. Erfolg "Fall, und Funktionsmechanik heben diese Funktionen, um den Prüfcode für Sie anzuwenden ... so müssen Sie sich nicht wirklich sorgen über Code-Bloat oder Wartungsprobleme, die Sie im imperativ-Stil-Code erhalten! –

3

Die anderen Antworten sind großartig, Berechnungsausdrücke wären perfekt dafür.

Nur um eine weitere Option zu bieten, ist es erwähnenswert, dass es einige Sonderfälle in der F # -Code-Struktur gibt, die eine weniger schmerzhafte "Rückkehr-frühe" Geschichte erlauben.

Die kanonische indent-basierte Formatierung könnte dieses Durcheinander:

let step1 = ... 
if failed step1 then 
    () // bail out 
else 
    let step2 = ... 
    if failed step2 then 
     () 
    else 
     let step3 = ... 
     if failed step3 then 
      () 
     else 
      let step4 = ... 
      ... 

Zwei alternative Formatierungen unten sind. Sie sehen komisch aus, sind aber sehr praktisch.

let step1 = ... 
if failed step1 then 
    () // bail out 
else 

let step2 = ... 
if failed step2 then 
    () 
else 

let step3 = ... 
if failed step3 then 
    () 
else 

let step4 = ... 
... 

oder

let step1 = ... 
if failed step1 then() else 

let step2 = ... 
if failed step2 then() else 

let step3 = ... 
if failed step3 then() else 

let step4 = ... 
... 
0

Daniels Antwort ist syntaktischer Zucker Fortsetzung Stil Ansatz. Hier ist die de-gezuckerte Version:

let step1 parm cont = 
    if true then cont 42 else None 
let step2 parm cont = 
    if false then cont 69 else None 
let conti parm = 
    step1 parm (fun result1 -> 
    step2 parm (fun result2 -> 
    Some(result1 + result2))) 
Verwandte Themen