# RubyでMabyeモナドを実装してみた。
_published: 2010/09/17_ 
最近、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モナド限定です。