uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

Groovyでリストモナド(orリストアプリカティブ)を書いてみる

Groovyでモナドを書くシリーズその3、「Groovyでリストモナド(orリストアプリカティブ)を書いてみる」です。(参考: GroovyでMaybeモナドを書いてみたGroovyでStateモナドを書いてみる)。

Haskellではリストは、「要素が非決定計算のそれぞれの可能性を表す」モナドとして見做すことができます(そういう風にみなすためのbindが定義されている)。

なのでおもしろいことができるのですが、それをGroovyで真似て見ようというわけです。

List.metaClass.rightShiftUnsigned = { Closure c -> // Haskell's >>=
    delegate.collect(c).inject([], {acc,elem->acc+elem}) // concatMap
}
// instance Applicative [] where
//   fs <*> xs = [f x | f <- fs, x <- xs]
List.metaClass.multiply = { List xs -> // Haskell's <*>
    List fs = delegate
    fs >>> {f->
    xs >>> {x->
        assert f instanceof Closure
        if (f.parameterTypes.size() > 1) {
            // Haskellの様に引数が足り無ければ自動的に
            // 部分適用になるということは無いので、明示的に部分適用。
            return [f.curry(x)]
        }
        else {
            return [f(x)]
        }
    }}
}

// リストモナドは非決定計算(可能性の計算)を表す。
// つまり「1 or 2 or 3」と「4 or 5 or 6」を掛け算した結果は、
// 「4 or 5 or 6 or 8 or 10 or 12 or 12 or 15 or 18」となる。
listManip1 = [1,2,3] >>> {x->      // listManip1 = do x <- [1,2,3]
             [4,5,6] >>> {y->      //                 y <- [4,5,6]
             [x*y]                 //                 pure x*y
             }}
assert listManip1 == [1*4, 1*5, 1*6, 2*4, 2*5, 2*6, 3*4, 3*5, 3*6]
println listManip1

// アプリカティブは、箱の中に対する透過的な演算を可能とする。
// モナドはアプリカティブでもある。
// リストモナド(非決定計算)に対して通常の関数(1引数関数(x+1))を適用する。
listManip2 = [{it+1}] * [1,2,3]   // listManip2 = pure (+1) <*> [1,2,3]
assert listManip2 == [2,3,4]
println listManip2

// アプリカティブは、箱の中に対する透過的な演算を可能とする。
// モナドはアプリカティブでもある。
// リストモナド(非決定計算)に対して通常の関数(2引数関数x+y)を適用する。
listManip3 = [{x,y->x+y}] * [1,2,3] * [4,5,6]
// listManip3 = pure (+) <*> [1,2,3] <*> [4,5,6]
assert listManip3 == [1+4, 1+5, 1+6, 2+4, 2+5, 2+6, 3+4, 3+5, 3+6]
// 「1 or 2 or 3」と「4 or 5 or 6」を足し算した結果は、
// 「5 or 6 or 7 or 6 or 7 or 8 or 7 or 8 or 9」となる。
println listManip3

ちなみに上では、Haskellでは「型クラス」であるApplicativeやMonadを、Groovyのクラスとして表現できていません。「データ.操作()」というパターンしか継承の対象にできない、GroovyやJavaなどのクラスベースの型システムの限界があるのですが、リストにメタクラスでオペレーションを付け足しして強引に突破してます。ある意味わかりやすい。

ちなみに上のコード中に出てくる

listManip1 = [1,2,3] >>> {x->      // listManip1 = do x <- [1,2,3]
             [4,5,6] >>> {y->      //                 y <- [4,5,6]
             [x*y]                 //                 pure x*y
             }}

は、[ x*y | x<- [1,2,3], y<-[4,5,6] ]というリスト内包表記というのがHaskellにあって、それはこのようなリストモナドに対する操作のシンタックスシュガーとして実現されています。コメントにあるのが、もう一つ別のdo記法というやつでこれもモナド操作のシンタックスシュガーです。Scalaだとfor{…}というモナド内包表記になるのかな(良く知らない)。


お勧めの本「すごいHaskellたのしく学ぼう!」Kindle版

すごいHaskellたのしく学ぼう!
オーム社 (2012-09-21)
売り上げランキング: 2,943

お勧めの本「すごいHaskellたのしく学ぼう!」Kindleじゃない版。

すごいHaskellたのしく学ぼう!
Miran Lipovača
オーム社
売り上げランキング: 15,643