Prev: イントロダクション | TOC: 目次 | Next: class で使う |
この章をつうじて、Maybe
型構築子を使うので、先にすすむ前に
Maybe
の使いかたと定義になれておかなければなりません。
Haskell のモナドを理解するためには、型構築子の扱いになじんでいる必要があ
ります。型構築子 は多相型をもちいたパラメータ化された型定義です。
Haskell では、一つ以上の具体的な型にたいして、型構築子を適用すると
新しい具体的な型を構築できます。Maybe
の定義は、
data Maybe a = Nothing | Just aです。
Maybe
は型構築子で Nothing
および
Just
はデータ構築子です。Just
データ構築子を
ある値に適用してデータ値を構成できます。
country = Just "China"同様に、
Maybe
型構築子をある型に適用して型を構成できます。
lookupAge :: DB -> String -> Maybe Int
多相型は多くの異る型の値を保持できるコンテナのようなものです。それゆえ、
Maybe Int
は Int
の値(あるいは
Nothing
)を保持する Maybe
コンテナと看倣せ、
また、Maybe String
は String
の値(あるいは
Nothing
)を保持する Maybe
コンテナと看倣せます。
Haskell ではこのコンテナの型も多相にすることができます。それゆえ、
「m a
」と書いて、ある型の値を保持するある型のコンテナを表現
することができます。
計算の抽象的な機能を表現するのに型構築子とともに型変数をよく使います。
たとえば、多相型 Maybe a
は、ある値または
Nothing
を返し得るすべての計算の型です。同様に、コンテナが
どのようなものを保持するかという詳細から離れて、コンテナの性質について
議論することができます。
モナドを使っていて、コンパイラが「kind errors」に関するエラーを出したら、
それは、型構築子を正しく使っていないという意味です。
Haskell ではモナドは型構築子(これを m
と呼ぶことにします)、
その型の値を構築する関数(a -> m a
)、および、
その型の値とその型の値を生成する計算とを組合せて、その型の新しい計算を
生成する関数
(m a -> (a -> m b) -> m b
)
で表現されます。ここで、コンテナは同じですが、コンテナの内容の型は変りう
ることに注意してください。モナドの型構築子を「m
」とするのは、
モナドに関して一般的な議論をするときの習慣です。この型の値を構築する関数
は伝統的には「return
」とよばれます。また 3 つめの関数は
「bind
」として知られていますが、「>>=
」と書きます。
これらの関数のシグネチャは
-- モナド m の型 data m a = ... -- return はモナドのインスタンスを作る型構築子 return :: a -> m a -- bind はモナドのインスタンス m a と、 -- a から別のモナドのインスタンス m b を作る計算と -- を組み合わせて新しいモナドのインスタンス m b を作る (>>=) :: m a -> (a -> m b) -> m b |
おおざっぱに言うと、モナドの型構築子は計算の型を定義し、
return
関数はこの計算の型のプリミティブな値を生成し、
>>=
はこの型の計算を組み合わせて、この型のより複雑な計算を
作り出します。コンテナのアナロジーを用いれば、型構築子 m
は
異なる値を保持できるコンテナです。m a
は型 a
の値を保持するコンテナです。return
関数は一つの値を、一つの
コンテナにいれる関数です。>>=
関数はモナドからその値をとり、
それを新しい値(別の型の可能性もある値)を含むモナドコンテナをつくる関数に
渡します。>>=
関数は、モナドコンテナ中の値を関数の第一引数
に束縛するので、「bind」として知られています。この束縛関数にロジックを追
加することで、モナドはそのモナド中の計算を組合せる特定の戦略を実装するこ
とができます。
このことは、下の例を見たあとならわかりやすくなってきますが、この点について、 ひどく訳がわからない感じがするなら、次に進む前に、ここの モナドの物理的なアナロジーを見ると いいでしょう。
羊のクローン実験の記録をとるプログラムを書いているとしましょう。すべての
羊の遺伝履歴を知りたいでしょうから、mother
と
father
という関数が必要になるでしょう。でも、クローン羊なの
で常に母と父の両方がいるとは限りません。
Haskell のコードでは型構築子 Maybe
をつかって、母あるいは
父をもたない可能性を表現しましょう。
type Sheep = ... father :: Sheep -> Maybe Sheep father = ... mother :: Sheep -> Maybe Sheep mother = ... |
こうすると、祖父を見つける関数の定義は少し複雑です。どちらかの親をもたない 可能性を処理しなければならないからです。
maternalGrandfather :: Sheep -> Maybe Sheep maternalGrandfather s = case (mother s) of Nothing -> Nothing Just m -> father m |
曾祖父を見つけようとするとさらに状況は悪くなります。
mothersPaternalGrandfather :: Sheep -> Maybe Sheep mothersPaternalGrandfather s = case (mother s) of Nothing -> Nothing Just m -> case (father m) of Nothing -> Nothing Just gf -> father gf |
醜く、不明瞭で、保守しにくいばかりでなく、これではあまりに面倒です。
計算途中での Nothing
値はどこにあっても最終的な結果を
Nothing
にしてしまうということは明かです。このことの記述を
一箇所に集約し、コードのなかにまき散らされているすべての明示的
case
による検査を取り除けば、コードはずっと良くなる
ことでしょう。こうすれば、コードは書きやすく、読みやすく、変更しやすい
ものになるでしょう。よいプログラミングスタイルは求める振舞いを捕捉する
組合せ子をつくることです。
example1.hs で利用可能なコード |
---|
-- comb は Maybe を返す操作の並びに対する組合せ子 comb :: Maybe a -> (a -> Maybe b) -> Maybe b comb Nothing _ = Nothing comb (Just x) f = f x -- こうすると `comb` をつかって複雑な並びを構築できる mothersPaternalGrandfather :: Sheep -> Maybe Sheep mothersPaternalGrandfather s = (Just s) `comb` mother `comb` father `comb` father |
この組合せ子は大成功です。コードはずっと明瞭で書きやすく、理解も変更もし
やすいものになりました。comb
関数は完全に多相的であることに
も注目してください。これは決して Sheep
に特定化されたもので
はないということです。実際、この組合せ子は、値を返すのに失敗する可能
性のある計算を合成するための一般的戦略を捕捉しています。 したがって、
同じこの組合せ子を、データベースへの問い合わせや辞書の検索などのように、
値を返せない可能性のある計算に適用することができます。
嬉しい結果は、プログラミング実践の常識が企らずもモナドの創造につながった
ということです。Just
関数(return
のように振舞う)
をともなう型構築子 Maybe
と、いま定義した組合せ子
(>>=
のように振舞う)とで、値を返さない可能性のある計算を
構築する単純なモナドを構成します。このモナドを本当に役に立つものにするた
めにあとやるべきことは、Haskell 言語で構築されたモナドのフレームワークに
すりあわせことだけです。これは次章の主題です。
型構築子 Maybe
が、値を返さない可能性のある計算を構築するた
めのモナドであることを見ました。もうひとつ、Haskell ではおなじみのリスト
を構築する型構築子 []
がモナドであると知ればびっくりするかも
しれませんね。リストモナドを使えば、0個、1個、あるいはもっと多くの値を返
しうる計算を構成することができます。
リストの return
関数は単純に一要素のリストを生成します
(return x = [x]
)。リストの束縛演算は元のリス
トに含まれるすべての値にたいして、関数を適用した結果を含む新しいリストを
生成します
(l >>= f = concatMap f l
)。
リストを返す関数のひとつの使い道は、曖昧な計算 — すなわち、 0個、1個あるいはそれ以上の出力を許すような計算を表現することです。 曖昧な部分計算から組み合わされた計算のなかでは、曖昧さも合成されます。 あるいは、偶々一つの値だけの出力も可能であり、ひとつの出力もないというこ とも可能です。この過程において、可能な計算状態の集合はリストによって表現 されます。したがって、リストモナドは、曖昧な計算のすべての許されたすべて パスにそった、同時並行計算を行う戦略を具現しています。
List モナドのこの使い方の例と、Maybe モナドの例との対比はすぐに示します。 しかし、まず、Haskellでは有用なモナドがどのように定義されているかをみて おかなければなりません。
モナドが、型構築子と、return
と呼ばれる関数と、
bind
あるいは >>=
とよばれる組合せ子関数で
あることを見ました。この3つの要素が共に機能して、計算の合成戦略を
カプセル化し、さらに複雑な計算を創りだします。
型構築子 Maybe
を用いて、良いプログラミングの実践により、
それぞれが値を返すのに失敗するような計算の並びから複雑な計算を
構築するために利用できる単純なモナドをどのように定義するかを見ました。
Maybe
モナドの結果は値を返さないかもしれない計算を合成する
戦略をカプセル化しています。モナド中のこの戦略をプログラムコードにする
ことで、アドホックな方法で組合せた計算にはない、モジュラリティと
柔軟性を達成できました。
また、もうひとつのよくある Haskell の型構築子、[]
もモナド
であることを見ました。リストモナドは 0個、1個あるいはそれ以上の複数の
値を返し得る計算の組合せ戦略をカプセル化したものです。
Prev: イントロダクション | TOC: 目次 | Next: class で使う |