2012-06-10 18 views
8

Lassen Sie uns sagen, dass wir folgende Berechnung implementieren möchten:Nesting-Funktion ruft in GO

outval/err = f3(f3(f1(inval))

wo jeder f1, f2, f3 kann zu diesem Zeitpunkt wir die Berechnung mit einem Fehler fehl stoppen und setzen err auf Fehler, der von der fehlerhaften Funktion zurückgegeben wird. (Natürlich kann Verschachtelung beliebig lang sein)

In Sprachen wie C++/Java/C# kann es leicht, indem f1 erfolgen, f2 und f3 eine Ausnahme aus und umschließt die Berechnung in einem Try-Catch-Block, während in Sprachen wie Haskell können wir stattdessen Monaden verwenden.

Jetzt versuche ich es in GO zu implementieren und der einzige Ansatz, den ich mir vorstellen kann, ist offensichtlich, wenn - sonst Leiter, die ziemlich ausführlich ist. Ich habe kein Problem, wenn wir die Aufrufe nicht verschachteln können, aber meiner Meinung nach fügt das Hinzufügen einer Fehlerüberprüfung nach jeder Zeile im Code hässlich aus und es unterbricht den Fluss. Ich würde gerne wissen, ob es einen besseren Weg gibt, es zu tun.

Edit: Bearbeiten gemäß dem Kommentar von peterSO
Unter dem konkreten Beispiel ist und einfache Implementierung

package main 

import "fmt" 

func f1(in int) (out int, err error) { 
    return in + 1, err 
} 

func f2(in int) (out int, err error) { 
    return in + 2, err 
} 

func f3(in int) (out int, err error) { 
    return in + 3, err 
} 

func calc(in int) (out int, err error) { 
    var temp1, temp2 int 
    temp1, err = f1(in) 
    if err != nil { 
     return temp1, err 
    } 
    temp2, err = f2(temp1) 
    if err != nil { 
     return temp2, err 
    } 
    return f3(temp2) 
} 

func main() { 
    inval := 0 
    outval, err := calc3(inval) 
    fmt.Println(inval, outval, err) 
} 

Was ich versuche Funktion calc zu erläutern ist, hat einige Berechnung möglicherweise mit Hilfe von Bibliotheksfunktionen Dies kann fehlschlagen und die Semantik ist, wenn ein Aufruf fehlschlägt. calc leitet den Fehler an den Aufrufer weiter (ähnlich wie die Ausnahme nicht behandelt wird). Meiner Meinung nach ist code für calc hässlich.

Zwischen für diesen speziellen Fall, in dem alle Bibliotheksfunktionen exakt gleiche Signatur haben, können wir den Code besser machen (Ich verwende Idee von http://golang.org/doc/articles/wiki/#tmp_269)

func saferun(f func (int) (int, error)) func (int, error) (int, error) { 
    return func (in int, err error) (int, error) { 
     if err != nil { 
      return in, err 
     } 
     return f(in) 
    } 
} 

Dann können wir calc als

neu definieren
func calc(in int) (out int, err error) { 
    return saferun(f3)(saferun(f2)(f1(in))) 
} 

oder als

func calc(in int) (out int, err error) { 
    sf2 := saferun(f2) 
    sf3 := saferun(f3) 
    return sf3(sf2(f1(in))) 
} 

Aber ohne Generika s Ich bin mir nicht sicher, wie ich diesen Ansatz für einen Satz von Bibliotheksfunktionen verwenden kann.

Antwort

5

Die Diskussion zwischen Fehlern und Ausnahmen ist lang und langwierig. Ich werde deshalb nicht darauf eingehen.

Die einfachste Antwort auf Ihre Frage betrifft Go eingebaute in defer, panic und recover Funktionen wie in this blog Beitrag diskutiert. Sie können ein Verhalten ähnlich wie Ausnahmen bieten.

package main 

import "fmt" 

func main() { 
    defer func() { 
     // This recovers from a panic if one occurred. 
     if x := recover(); x != nil { 
      fmt.Printf("%v\n", x) 
     } 
    }() 

    value := f(f(f(1))) 
    fmt.Printf("%d\n", value) 
} 

func f(i int) int { 
    value := i*i + 1 

    // something goes wrong, panic instead of returning an error. 
    panic("ohnoes") 

    return value 
} 
+0

Danke für die Antwort, aber ich bin mir nicht sicher, ob ich Ihren Ansatz verstehe. Was passiert, wenn ich die Signatur von f1, f2, f3 etc. nicht kontrolliere, weil sie Bibliotheksfunktionen sind.In meiner bescheidenen Meinung erfordern Fehlerbehandlungsinfrastrukturen in anderen Sprachen nicht, dass Sie solch eine Kontrolle haben – Suyog

+2

Suyog, zitierend von Ihrer ursprünglichen Frage, "... getan, indem f1, f2 und f3 eine Ausnahme werfen ..." Es sicher klingt wie du und erlaubst das Ändern von f1, f2 und f3, um Ausnahmen auszulösen. Panik zu haben ist nicht anders. Panik ist der in Go verfügbare Mechanismus, um den Stapel von beliebiger Tiefe abzuwickeln und dabei einen Wert zurückzugeben. Es ist nicht der idiomatische und bevorzugte Weg, Fehler in Go zu behandeln, aber es ist der Mechanismus, der das tut, was Sie fragen. – Sonia

+0

Ich meinte, f1, f2, f3 sind bereits definiert, um eine Ausnahme zu werfen. Entschuldigung für schlechte Formulierungen. In einer Sprache wie JAVA sind viele Bibliotheksfunktionen so definiert, dass sie eine Ausnahme auslösen, während es in GO anscheinend so ist, dass Bibliotheksfunktionen einen Fehler zurückgeben. Das Problem, vor dem ich stehe, ist, dass ich Fehler sofort überprüfen muss, was dazu führt, dass sich repetitiver Code schreibt. – Suyog

0

Ohne ein konkretes Beispiel, kippen Sie auf Windmühlen. Zum Beispiel geben fn-Funktionen nach Ihrer Definition einen Wert und einen Fehler zurück. Die Fn-Funktionen sind Paketfunktionen, deren Signatur nicht geändert werden kann. Verwenden Sie Ihr Beispiel

package main 

import "fmt" 

func f1(in int) (out int, err error) { 
    return in + 1, err 
} 

func f2(in int) (out int, err error) { 
    return in + 2, err 
} 

func f3(in int) (out int, err error) { 
    return in + 3, err 
} 

func main() { 
    inval := 0 
    outval, err := f3(f2(f1(inval))) 
    fmt.Println(inval, outval, err) 
} 

Wie erhalten Sie Ihr Beispiel zum Kompilieren und Ausführen?

+0

Danke für die Antwort, ich aktualisiere die Frage nach den Vorschlägen – Suyog

7

Wenn Sie das wirklich können, können Sie eine Compose-Funktion verwenden.

func compose(fs ...func(Value) (OutVal, error)) func(Value) (OutVal, error) { 
    return func(val Value) OutVal, Error { 
    sVal := val 
    var err error 
    for _, f := range fs { 
     sval, err = f(val) 
     if err != nil { 
     // bail here and return the val 
     return nil, err 
     } 
    } 
    return sval, nil 
    } 
} 

outVal, err := compose(f1, f2)(inVal) 

die meiste Zeit, obwohl Sie wahrscheinlich als dies sein einfacher wollen, da es für andere schwierig sein kann, den Code zu verstehen, wenn sie es zu begegnen.

+0

Das war nützlich, danke! – Suyog

7

Zuerst eine erweiterte Version des Try-Catch-Stils, an den Sie gewöhnt sind, der sich offensichtlich aus jimts Antwort und PeterSOs Antwort entlehnt.

package main 

import "fmt" 

// Some dummy library functions with different signatures. 
// Per idiomatic Go, they return error values if they have a problem. 
func f1(in string) (out int, err error) { 
    return len(in), err 
} 

func f2(in int) (out int, err error) { 
    return in + 1, err 
} 

func f3(in int) (out float64, err error) { 
    return float64(in) + .5, err 
} 

func main() { 
    inval := "one" 

    // calc3 three is the function you want to call that does a computation 
    // involving f1, f2, and f3 and returns any error that crops up. 
    outval, err := calc3(inval) 

    fmt.Println("inval: ", inval) 
    fmt.Println("outval:", outval) 
    fmt.Println("err: ", err) 
} 

func calc3(in string) (out float64, err error) { 
    // Ignore the following big comment and the deferred function for a moment, 
    // skip to the comment on the return statement, the last line of calc3... 
    defer func() { 
     // After viewing what the fXp function do, this function can make 
     // sense. As a deferred function it runs whenever calc3 returns-- 
     // whether a panic has happened or not. 
     // 
     // It first calls recover. If no panic has happened, recover returns 
     // nil and calc3 is allowed to return normally. 
     // 
     // Otherwise it does a type assertion (the value.(type) syntax) 
     // to make sure that x is of type error and to get the actual error 
     // value. 
     // 
     // It does a tricky thing then. The deferred function, being a 
     // function literal, is a closure. Specifically, it has access to 
     // calc3's return value "err" and can force calc3 to return an error. 
     // A line simply saying "err = xErr" would be enough, but we can 
     // do better by annotating the error (something specific from f1, 
     // f2, or f3) with the context in which it occurred (calc3). 
     // It allows calc3 to return then, with this descriptive error. 
     // 
     // If x is somehow non-nil and yet not an error value that we are 
     // expecting, we re-panic with this value, effectively passing it on 
     // to allow a higer level function to catch it. 
     if x := recover(); x != nil { 
      if xErr, ok := x.(error); ok { 
       err = fmt.Errorf("calc3 error: %v", xErr) 
       return 
      } 
      panic(x) 
     } 
    }() 
    // ... this is the way you want to write your code, without "breaking 
    // the flow." 
    return f3p(f2p(f1p(in))), nil 
} 

// So, notice that we wrote the computation in calc3 not with the original 
// fX functions, but with fXp functions. These are wrappers that catch 
// any error and panic, removing the error from the function signature. 
// Yes, you must write a wrapper for each library function you want to call. 
// It's pretty easy though: 
func f1p(in string) int { 
    v, err := f1(in) 
    if err != nil { 
     panic(err) 
    } 
    return v 
} 

func f2p(in int) int { 
    v, err := f2(in) 
    if err != nil { 
     panic(err) 
    } 
    return v 
} 

func f3p(in int) float64 { 
    v, err := f3(in) 
    if err != nil { 
     panic(err) 
    } 
    return v 
} 
// Now that you've seen the wrappers that panic rather than returning errors, 
// go back and look at the big comment in the deferred function in calc3. 

Also, Sie könnten protestieren, dass Sie für einfacher und das ist nicht gefragt. Kein Argument im Großen und Ganzen, aber wenn die Bibliothek alle Rückgabewerte verarbeitet und Sie Funktionsaufrufe ohne die Fehlerwerte verketten möchten, besteht die Lösung darin, die Bibliotheksfunktionen zu umbrechen, und die Wrapper sind sehr dünn und einfach zu schreiben. Der einzige andere schwierige Teil ist die verzögerte Funktion, aber es ist ein Muster, das Sie lernen und wiederverwenden können und es sind nur ein paar Zeilen Code.

Ich möchte diese Lösung nicht zu sehr fördern, weil sie nicht oft verwendet wird. Es ist jedoch ein gültiges Muster, und es hat einige Anwendungsfälle, wo es angebracht ist.

Fehlerbehandlung ist ein großes Thema, wie jimt erwähnt. "Was sind gute Möglichkeiten, Fehler in Go zu beheben?" wäre eine gute Frage für SO außer für das Problem, dass es das "ganze Buch" kriiteon verfehlt. I kann man sich ein ganzes Buch zum Thema Fehlerbehandlung in Go vorstellen.

Stattdessen werde ich meine allgemeine Beobachtung anbieten, dass, wenn Sie gerade anfangen, mit Fehlerwerten zu arbeiten, anstatt zu versuchen, sie verschwinden zu lassen, nach einer Weile beginnen Sie, die Vorteile zu verstehen. Was aussieht wie eine ausführliche Ladder of if-Anweisungen in einem Spielzeug-Beispiel, wie wir es hier verwendet haben, könnte immer noch wie eine ausführliche Leiter von if-Anweisungen aussehen, wenn Sie es erstmals in einem realen Programm schreiben. Wenn Sie jedoch tatsächlich mit diesen Fehlern umgehen müssen, kommen Sie zurück zum Code und sehen ihn plötzlich als Stubs, die darauf warten, dass Sie mit echtem Code zur Fehlerbehandlung arbeiten. Sie können sehen, was zu tun ist, weil der Code, der den Fehler verursacht hat, genau dort ist. Sie können einen Benutzer daran hindern, eine obskure Fehlermeldung auf niedriger Ebene zu sehen und stattdessen etwas Sinnvolles anzuzeigen. Sie als Programmierer werden aufgefordert, das Richtige zu tun, anstatt eine Standard-Sache zu akzeptieren.

Für umfassendere Antworten, eine gute Ressource zu beginnen mit ist der Artikel Error Handling and Go. Wenn Sie die Go-Nuts messages durchsuchen, gibt es auch dort lange Diskussionen. Die Funktionen in der Standardbibliothek rufen sich gegenseitig ziemlich (Überraschung) auf, und so enthält der Quellcode der Standardbibliothek viele Beispiele für Handhabungsfehler. Dies sind hervorragende Beispiele, die zu studieren sind, da der Code von Go-Autoren geschrieben wurde, die diesen Programmierstil für das Arbeiten mit Fehlerwerten propagieren.

+0

Vielen Dank für ausführliche Antwort! – Suyog

0

Gefunden Mailing thread auf Go-Nuts für dieses Thema. Hinzufügen als Referenz.

0

Schade, dieses bereits geschlossen ist ... Das:

value := f(f(f(1))) 

kein Beispiel für Verkettungs ist aber der Verschachtelung. Chaining sollte in etwa so aussehen: Hier

c.funA().funB().funC() 

ist ein Arbeits example.

+0

Messepunkt; Das ist keine Antwort, aber ein Kommentar – leonbloy