10

次の計算を実装したいとしましょう。

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

f1、、のそれぞれがその時点でエラーで失敗する可能性がある場合、計算を停止し、失敗した関数によって返されるエラーに設定f2します。(もちろん、ネストは任意に長くすることができます)f3err

C ++ / JAVA / C#のような言語では、例外をスローして計算をtry-catchブロックで囲むことで簡単に実行できますがf1f2Haskellf3のような言語では代わりにモナドを使用できます。

今、私はそれをGOで実装しようとしていますが、私が考えることができる唯一のアプローチは、明らかなif-elseラダーであり、かなり冗長です。呼び出しをネストできなくても問題はありませんが、私の意見では、コードの各行の後にエラーチェックを追加すると見苦しくなり、フローが中断されます。もっと良い方法があるか知りたいのですが。

編集:peterSOによるコメントに従って編集
以下は具体的な例と簡単な実装です

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)
}

私が説明しようとしているのは、関数calcは、失敗する可能性のあるライブラリ関数の助けを借りて計算を行うことです。セマンティクスは、呼び出しが失敗した場合、calcがエラーを呼び出し元に伝播します(例外を処理しない場合と同様)。私の意見では、calcのコードは醜いです。

すべてのライブラリ関数がまったく同じ署名を持っているこの特定のケースの間に、コードを改善することができます(私は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)
    }
} 

次に、calcを次のように再定義できます。

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

またはとして

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

しかし、ジェネリックスのサポートがなければ、このアプローチをライブラリ関数のセットにどのように使用できるかわかりません。

4

6 に答える 6

7

本当にこれができるようにしたい場合は、作成機能を使用できます。

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)

ただし、ほとんどの場合、他の人がコードに遭遇したときにコードを理解するのが難しい場合があるため、これよりも簡単になりたいと思うでしょう。

于 2012-06-10T18:49:11.910 に答える
7

まず、慣れ親しんだ try-catch スタイルの拡張バージョンで、明らかに jimt の回答と PeterSO の回答から借りています。

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.

それで、あなたはもっと簡単に頼んだことに抗議するかもしれませんが、これはそうではありません. 全体として引数はありませんが、ライブラリ関数がすべてエラー値を返し、エラー値なしで関数呼び出しを連鎖させたい場合、利用可能な解決策はライブラリ関数をラップすることであり、ラッパーは非常に薄くて簡単に記述できます。他の唯一の難しい部分は遅延関数ですが、これは学習して再利用できるパターンであり、ほんの数行のコードです。

このソリューションは頻繁に使用されるものではないため、あまり宣伝したくありません。ただし、これは有効なパターンであり、適切な使用例がいくつかあります。

jimt が述べたように、エラー処理は大きなテーマです。「Go でエラー処理を行う良い方法は何ですか?」「本全体」の基準に失敗するという問題を除いて、SOにとっては良い質問です。Go でのエラー処理については、1 冊の本を想像できます。

代わりに、エラー値を消去しようとするのではなく、単にエラー値を処理し始めると、しばらくすると、これを行うことの利点を理解し始めるという私の一般的な観察を提供します。ここで使用したようなおもちゃの例で if ステートメントの冗長なはしごのように見えるものは、実際のプログラムで最初に記述したときには、if ステートメントの冗長なはしごのように見えるかもしれません。しかし、実際にこれらのエラーを処理する必要がある場合、コードに戻ると、突然、実際のエラー処理コードで具体化するのを待っているスタブとして表示されます。エラーの原因となったコードがすぐそこにあるため、何をすべきかがわかります。あいまいな低レベルのエラー メッセージがユーザーに表示されないようにし、代わりに意味のあるものを表示することができます。プログラマーとして、デフォルトのことを受け入れるのではなく、正しいことを行うように求められます。

より包括的な回答については、まず、記事Error Handling and Go を参考にしてください。Go-Nutsのメッセージを検索すると、この問題に関する長い議論もそこにあります。標準ライブラリ内の関数は相互にかなり頻繁に呼び出しを行うため (驚き)、標準ライブラリのソース コードにはエラー処理の例が多数含まれています。これらは、エラー値を処理するこのプログラミング スタイルを推進している Go の作成者によってコードが記述されているため、学習するのに優れた例です。

于 2012-06-11T02:00:57.800 に答える
6

エラーと例外の間の議論は長くて退屈なものです。したがって、私はそれに立ち入りません。

あなたの質問に対する最も簡単な答えは、このブログ投稿で説明されているように、Go の組み込みのdeferpanic、およびrecover関数に関するものです。例外と同様の動作を提供できます。

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
}
于 2012-06-10T09:24:13.143 に答える
0

go-nuts でこのトピックのメーリングスレッドを見つけました。参考までに追記。

于 2012-06-13T07:34:56.113 に答える
-1

具体的な例がなければ、あなたは風車に向かって傾いています。たとえば、定義ごとに、fn 関数は値とエラーを返します。fn 関数は、署名を変更できないパッケージ関数です。あなたの例を使用して、

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)
}

サンプルをどのようにコンパイルして実行しますか?

于 2012-06-10T13:06:28.170 に答える
-1

残念ながら、これはすでに閉鎖されています...これ:

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

チェーンの例ではなく、ネストの例です。連鎖は次のようになります。

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

これが実際のです。

于 2013-06-21T19:53:59.337 に答える