Prev: モナドに触れる | TOC: 目次 | Next: モナド則 |
この章での議論は Haskell の型クラスシステムに関連しています。 Haskell の型クラスに馴染みがない向きには、先へ行く前に これで復習してください。
Haskell にはふたつのモナド関数 return
および
>>=
の名前とシグネチャとを定義している標準の
Monad
があります。厳密にいえば、ユーザが定義したモナドを
この Monad
クラスのインスタンスとする必要はありません。
しかし、そうしておく方がよいでしょう。Haskell は言語中に組込まれた
Monad
クラスのインスタンスに対して特別なサポートをします。
ユーザが定義したモナドを Monad
クラスのインスタンスとすると、
そうした機能を使って、より明解で、エレガントなコードを書くことができます。
さらに、ユーザが定義したモナドを Monad
クラスのインスタンス
とすることで、コードを読む他の人に重要な情報をもたらし、まぎらわしく、
標準的ではない関数の名前を使うことができないようにすることができます。
あなたの定義したモナドを Monad
インスタンスにすることは、簡
単ですし、得られるものもたくさんありますから、ぜひそうしましょう。
Haskell における標準の Monad
クラスの定義は
およそ次のようになっています。
class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a |
前ページの例題 をつづけましょう。
こんどは、型構築子 Maybe
を Monad
クラスの
インスタンスとして Haskell のモナドフレームワークに適合させましょう。
前に定義した Maybe
ではデータ構築子 Just
に
モナドの return
関数の役割をさせていたこと、そして、
モナドの >>=
束縛関数の役割をさせるのに
簡単なコンビネータを構成したことを思い出してください。
このモナドとしての役割を、明示的に Monad
クラスの
インスタンスであることを次のように Maybe
と宣言することに
よっておこなえます。
instance Monad Maybe where Nothing >>= f = Nothing (Just x) >>= f = f x return = Just |
いったん、Maybe
を Monad
クラスのインスタンス
であると定義してしまえば、複雑な計算を構成するのに、標準モナド演算子を利
用することができます。
-- 複雑な並びを構成するのにモナド演算子が使えます maternalGrandfather :: Sheep -> Maybe Sheep maternalGrandfather s = (return s) >>= mother >>= father fathersMaternalGrandmother :: Sheep -> Maybe Sheep fathersMaternalGrandmother s = (return s) >>= father >>= mother >>= mother |
Haskellでは、Maybe
は Monad
クラスのインスタンス
として標準プレリュードのなかで定義されてます。それゆえ、自分自身で
定義する必要はありません。この後見る別のモナド、すなわち、リスト構築子
も標準プレリュード中で、Monad
クラスのインスタンスとして
定義されています。
モナドに作用する関数を書くときには、特定のモナドインスタンスではなく、
Monad
クラスを使うようにしましょう。
doSomething :: (Monad m) => a -> m bという関数のほうが、
doSomething :: a -> Maybe bという関数よりもはるかに柔軟性があります。 前者は、多くのモナドの型について、対象となるモナドに埋め込まれた 戦略に依存する別々の振舞いを扱うことができますが、後者は厳密に、
Maybe
モナド戦略でしか扱えません。
標準のモナド関数名を使うことはよいことですが、
Monad
クラスに属していることの利点のもうひとつは Haskell の
「do 記法」のサポートです。do 記法は表現力のある速記法で、モナド計算を
構築するためのものです。これは、リスト上の計算を構築するのに、リスト
内包表記が強力な速記法であるのと似ています。Haskell では、あらゆる
Monad
クラスのインスタンスは、doブロック中で使うことが
できます。
要するに、この do 記法によりモナド計算を変数をつかった擬似命令型スタイル
で書くことができます。モナド計算の結果を、左向き矢印演算子
<-
をつかって、変数に「割当てる」ことができます。
そうしておいて、その変数をそのあとのモナド計算で使うと、自動的に
束縛が実行されます。矢印の右側の式の型はモナド型 m a
です。
その矢印の左側の式は、パターンで、そのモナドの内側の値に
マッチするパターンです。(x:xs)
は、たとえば、
Just [1,2,3]
にマッチします。
これは Maybe
モナドをつかった do 記法の例です。
プログラムコードは example2.hs |
---|
-- 複雑な並びを構築するのに do 記法も使えます mothersPaternalGrandfather :: Sheep -> Maybe Sheep mothersPaternalGrandfather s = do m <- mother s gf <- father m father gf |
これと、前述の do 記法を使わないで書いた
fathersMaternalGrandmother
を比べましょう。
上で示した do ブロックは、ブロックのエクステントを定義する レイアウトルールを用いて書かれています。Haskell では、ブレースと セミコロンを用いて、do ブロックを定義することもできます。
mothersPaternalGrandfather s = do { m <- mother s; gf <- father m; father gf } |
do 記法は命令型のプログラミング言語に似ており、そこでは、計算が より単純な計算の明示的な並べから構築されるということに注意してください。 この視点から見ると、モナドはより大きな規模の関数プログラム中で、 命令型スタイルを創り出す可能性があります。このテーマは、後で副作用と I/O モナドを扱う時にさらに拡張することになります。
do 記法は単なる構文糖衣です。標準のモナド演算子をつかってできないことは、 do 記法をつかってもできません。しかし、do 記法は、ずっと簡潔で便利な場合 があります。とくに、モナド計算の並びが長いときには簡潔で便利なものです。 標準のモナド束縛記法と do 記法の両方を理解し、それぞれを適切な場所で適用 できるようにしておくべきです。
Haskell はモナドに対する組み込みサポートを提供しています。Haskell の
モナドサポートを亨受するには、モナド型構築子を Monad
クラスのインスタンスとして宣言し、そのモナドに対応する
return
と >>=
(バインドと発音する)関数を定義し
なければなりません。
Monad
クラスのインスタンスであるモナドでは、do 記法が
使えます。この記法は構文糖衣で、モナド計算の記述に、単純な命令型
スタイルの記法を提供します。
Prev: モナドに触れる | TOC: 目次 | Next: モナド則 |