# RubyでMabyeモナドを実装してみた。 _published: 2010/09/17_ ![alt](http://b.hatena.ne.jp/entry/image/http://d.hatena.ne.jp/shunsuk/20100917/1284728273) 最近、Haskellやってません。今日は、Scalaの基本文法を読んでました。いいですね、Scala。いや、それよりHaskellを。モナド。モナド、意味わからん。理解しなくても使える気もしますが、それもなんだか気持ち悪い。モナドをある程度(圏論まで踏み込まず)理解するには、どうすればいいでしょうか。 Maybeモナド?それ、Rubyでできるよ。え、できるの?できるかな?やってみましょう。やりましょう。 失敗するかもしれない処理をうまいことやってくれるMaybeモナド。まず、Haskellで。サンプルコードは、「あどけない話」(Haskellと言えば!)から。 - [Haskell とモナド - あどけない話](http://d.hatena.ne.jp/kazu-yamamoto/20080208/1202456329) ```haskell db = [("alice", [("title", "Ms."), ("job", "sales")]), ("bob", [("title", "Mr."), ("job", "engineer")])] monad = return db >>= lookup "bob" >>= lookup "job" main = print monad -- Just "engineer" ``` わざと失敗してみると。 ```haskell monad = return db >>= lookup "chris" >>= lookup "job" -- Nothing ``` 1段階目で止めてみる。 ```haskell -- Just [("title","Mr."),("job","engineer")] ``` returnを省略して書くと。 ```haskell monad = lookup "bob" db >>= lookup "job" -- Just "engineer" ``` doを使うと。 ```haskell monad = do person <- lookup "bob" db lookup "job" person -- Just "engineer" ``` よくわからんけど、Rubyで書くよ。モナドはクラスで包めばいいんじゃないでしょうか。先に、こう書けるようになるというのを書いておきます。 ```ruby Maybe.ret(db).bind(lookup(:bob)).bind(lookup(:job)) ``` 実装です。(追記2010-09-18:`bind` がブロックを取っていましたが、`しっくりこないのでProc` を取るようにしました。) ```ruby class Maybe def self.ret(data) self.new(data) end def initialize(data) @data = data end def bind(f) f.call(@data) end def ==(other) @data == other.instance_eval {@data} end def to_s if self.class == Nothing "Nothing" else "Just #{@data.inspect}" end end end class Nothing < Maybe; end class Just < Maybe; end ``` `return` は、`ret` メソッド。`>>=` は、`bind` メソッドです。`to_s` は、モナドから中身を取り出すイメージです。 `Nothing` と `Just` を `Maybe` から派生させていいのか不明。 `lookup` 関数。Haskellだと部分適用ができますが、Rubyなので `lambda` で代用。 ```ruby def lookup(key) lambda {|data| data.key?(key) ? Just.ret(data[key]) : Nothing.ret(nil) } end ``` `lambda` がイヤなら、これを使って部分適用できます。 - [[Ruby 1.8で任意のメソッドをカリー化するメソッドをつくる。]] データを用意します。 ```ruby db = {:alice => {:title => "Ms.", :job => "sales"}, :bob => {:title => "Mr.", :job => "engineer"}} ``` これは、もちろんエラー。 ```ruby db[:chris][:title] ``` こういう書き方もできますが、 ```ruby data.fetch(:bob, {}).fetch(:title, nil) ``` これだと、キーが存在しなかったのか、値がnilだったのか判断できません。 `lookup` を使うと。 ```ruby puts lookup(:bob).call(db) #=> Just {:title=>"Mr.", :job=>"engineer"} puts lookup(:chris).call(db) #=> Nothing puts lookup(:nil).call :nil => nil #=> Just nil ``` さあ、本番です。 ```ruby monad = Maybe.ret(db).bind(lookup(:bob)) puts monad.to_s #=> Just {:title=>"Mr.", :job=>"engineer"} monad = Maybe.ret(db).bind(lookup(:bob)).bind(lookup(:job)) puts monad.to_s #=> Just "engineer" monad = Maybe.ret(db).bind(lookup(:chris)).bind(lookup(:job)) puts monad.to_s #=> Nothing ``` 最後の例では、`:chris` で失敗していますが、うまいこと処理されます。 モナドは、3つのモナド則を満たさねばなりません。 ```haskell (return x) >>= f == f x m >>= return == m (m >>= f) >>= g == m >>= (\x -> f x >>= g) ``` Maybeクラスはどうでしょうか? ```ruby # (return x) >>= f == fx puts Maybe.ret(db).bind(lookup(:bob)) == lookup(:bob).call(db) #=> true # m >>= return == m puts Maybe.ret(db).bind(lambda {|data| Maybe.ret(data) }) == Maybe.ret(db) #=> true # (m >>= f) >>= g == m >>= (\x -> f x >>= g) a = Maybe.ret(db).bind(lookup(:bob)).bind(lookup(:job)) b = Maybe.ret(db).bind(lambda {|x| lookup(:bob).call(x).bind(lookup(:job)) }) puts a == b #=> true ``` モナド則を満たしてますね。 とうわけで、RubyでMaybeモナドを実装してみました。 と、書いたのですが、正しいのかどうかすらわかりません。これが正しいとしたら、なんとなくモナドのイメージがつかめた気がします。あ。Maybeモナド限定です。