2009年11月8日日曜日

Haskell の Maybe a 型と Either a b 型 (2) - 類似性と違い

Haskell の Maybe a 型と Either a b 型 (1) の続き。

 

Maybe a 型と Either a b 型の類似性

Maybe a 型のコンストラクタは、

Nothing

Just a

この型の意味は 前回 見たように「何もないよの `Nothing’」 と「何かあったよ `Just a’」 で、関数によって取り出したい値が ある場合 と ない場合 を総称した概念を表現している。例えば、何かを検索するための関数の場合、値を見つけたときに Just で包み、見つからなかったら Nothing を返す。他の言語で null や None を返すような曖昧なことをするのではなく、「あった場合、なかった場合」を一つの型としてコンパイラがキッチリとチェックする。

 

この Maybe a 型と似ているのが Either a b 型。

either の言葉としての日常的な使い方は、

Either you or she is to go.
君か彼女かどちらかが行かねばならない

(Yahoo!辞書 – either より)

文字通り「どちらか一方」を表わす型。

どちらか一方」という意味では、Maybe a 型の Nothing と Just も「あるかないか」のどちらか一方を返す関数で使われる。

 

Either a b 型のコンストラクタを見ると、Maybe によく似ている。

Left a

Right b

Maybe と対応させて解釈すると都合がいい。

091028-007

ただし、各々を対応させたのは便宜的なもので、関数を適用した結果、二つの内どちらかに分類することがその本質。違いは、Maybe a 型が Nothing を返したら「もう後はおらぁ知らねぇ~」なのに対して、対応する Either a b 型は律儀にも Left で値を包んで返してくれる。

 

Maybe a 型と Either a b 型の違い

Maybe a 型の例

例えば、次のような状況を想定。

「合格ラインが 80 点のテスト」がある。受験生は合格ラインの得点を知らない。

これに対して、合否を判定する test 関数を定義し、 Maybe a 型を利用してみる。合格なら Just、不合格なら Nothing を返すとする。

goukaku = 80

test score = if score >= goukaku 
             then Just "Goukaku!"
             else Nothing

例えば、このテストで 80点、79点をとったとして test 関数を適用すると、

*Main> test 80
Just "Goukaku!"
*Main> test 79
Nothing

79点の人は問答無用の Nothing で門前払い。自分が何点合格まで足りなかったのかわからない。

 

Either a b 型の例

これでは受験生も納得できないだろうと考え、

「合格得点まで何点足りなかったのか?」

を返すように test 関数を変更することにした。

今度は Either a b 型を使い、合格を Right、不合格を Left で表現するとして test’ 関数を定義。

test' score = if score >= goukaku
              then Right "Goukaku!"
              else Left $ "Ato " ++ show (goukaku - score) ++ " ten tari nai"

先ほどと同じように、80点、79点をとったとして test’ 関数を適用すると

*Main> test' 80
Right "Goukaku!"
*Main> test' 79
Left "Ato 1 ten tari nai"

 

上記のように Maybe は Just で値を包まない限り Nothing が返され、それ以上何も伝えることができない。それに対して、Either では Maybe の Nothing に相当する Left でも何かを伝えることができる。

 

maybe と either 関数 の類似性

とは言ったものの、最初は Maybe a 型と Either a b 型がなぜ Prelude の Basic data types にあるのか全く見当がつかなかった。 (+_+)

Maybe によると、

Using Maybe is a good way to deal with errors or exceptional cases without resorting to drastic measures such as error.

これは 前回見た elemIndex 関数 のように、Maybe a 型は見つからなかったときに error で実行を止めてしまうのではなく、「なかった!」ということをこの関数の呼出し元に伝えるための方法であり、そのための型であるということ。

続いて Maybe の説明には次のように書かれている。

It is a simple kind of error monad, where all errors are represented by Nothing. A richer error monad can be built using the Data.Either.Either type.

(同上より)

この辺りで「ん?」 (@_@) … と。

Error モナドについては横に置いておくとして、エラーを伝える方法として Either は Maybe の親戚のようなものだと認識。

 

Data.Maybe, Data.Either から import されている関数      

ところで、Maybe, Either は、それぞれ Data.Maybe, Data.Either で定義されており、いくつか関数が定義されている。しかし、Prelude には各々一つの関数しか import されていない。それが以下の二つ。

maybe :: b -> (a -> b) -> Maybe a -> b

either :: (a -> c) -> (b -> c) -> Either a b -> c

最初、引数がごちゃごちゃしていてやだなぁ (@_@;) と感じた。しかし、説明を読めば、両者とも

第3引数 の値に応じて第1引数 もしくは 第2引数 が評価される関数

であることがわかる。

図示すれば、以下のように maybe と either の引数の間に対応関係がある。

091028-008

そして、第3引数の値が与えられると、返ってきた値に応じて第1引数、第2引数の式が評価される。

091102-003

either 関数に関しては、返ってきた値によって、文字通り Left または Rihgt にある関数が評価されるのでわかりやすい。

ちなみに、If-then-else – HaskellWiki には  if 式を関数に置き換えている例があるが、上記はこれに似ている。ただし、こちらは第1引数の値に応じて、第2第3引数が評価される。

if' :: Bool -> a -> a -> a
if' True  x _ = x
if' False _ y = y

… ということは、maybe, either 関数は、条件分岐の Maybe a, Either a b 型版と言ったところか。

追記 (2009.11.15)Data.Bool.HT パッケージに if’ が定義されている。(via Case – HaskellWiki )

 

試してみる

例えば、先ほどの test 関数の結果を第3引数に渡し、

「試験に合格した場合」と「不合格な場合」にかける言葉を返す hyouka 関数

を定義してみる。

hyouka = maybe "Zannen (;_;" (++ " omedetou! (^^") . test

例えば、

*Main> hyouka 80
"Goukaku! omedetou! (^^"
*Main> hyouka 79
"Zannen (;_;"

 091108-001

either 関数も同様に、

hyouka' = either (++ "nante zannen (+_+") (++ " omedetou! (^^") . test'

例えば、

"Goukaku! omedetou! (^^"
*Main> hyouka' 79
"Ato 1 ten tari nainante zannen (+_+"

 091108-002