Goとerror monads

GoCon 2013 autumnの後、yuroyoroなどとと飲んでいて

「Goのエラーを戻り値で引き回すのがどうも冗長」
Haskellのエラーモナドみたいな何かがあればいいのに」
「型パラメータがサポートされればそういうのが書きやすくなるのに」

という話をしていて、クロージャーで関数呼び出しを囲えば今の言語仕様でもいいんじゃないかなと思ってその場で書いてみたものの話。

monad というのがインターフェイスになっていて、次のメソッドを定義している。

  • Do(what func () (interface {}, error)) monad

戻り値であるmonadに対して次にDoもしくはEndが呼ばれたときに呼ぶべき関数を受け取り、その関数への参照を保持するmonadを生成して返す

  • DoIt() (interface{], error)
    • monadの保持している参照先の関数を呼ぶ。内部用のメソッドでDo()から呼び出される
  • End(errorfn func (error)) interface {}
    • モナドの過程のどこかでエラーが発生していたら errorfn を呼び、エラーがなければ最終的な戻り値を返す

そして、これらを実装する just と _error という型がある。

  • just の Do() の中で monad の保持している DoIt() を呼び、DoIt() の戻り値がエラーを示していれば _error を返し、正常ならば新しい just に Do() に渡された引数を入れて返す
  • _error の Do() は中で何もせず、ただ受け取った _error のコピーを返す

しかし

func main() {
    monad := &just {}
    monad.Do(func () (interface {}, error) {
        println("test1")
        return nil, nil
    }).Do(func () (interface {}, error) {
        println("test2")
        // return nil, nil
        return nil, errors.New("1")
    }).Do(func () (interface {}, error) {
        println("test3")
        return nil, nil
    }).Do(func () (interface {}, error) {
        println("test4")
        return nil, nil
    }).End(func (err error) {
        fmt.Printf("I got an error: %s\n", err.Error())
    })
}

こんなメソッドチェイン、誰も書きたいと思わない。