Prev: モナドに触れる TOC: 目次 Next: モナド則

クラスで使う


Haskell の型クラス

この章での議論は Haskell の型クラスシステムに関連しています。 Haskell の型クラスに馴染みがない向きには、先へ行く前に これで復習してください

Monad クラス

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

例題のつづき

前ページの例題 をつづけましょう。 こんどは、型構築子 MaybeMonad クラスの インスタンスとして Haskell のモナドフレームワークに適合させましょう。

前に定義した Maybe ではデータ構築子 Just に モナドの return 関数の役割をさせていたこと、そして、 モナドの >>= 束縛関数の役割をさせるのに 簡単なコンビネータを構成したことを思い出してください。 このモナドとしての役割を、明示的に Monad クラスの インスタンスであることを次のように Maybe と宣言することに よっておこなえます。

instance Monad Maybe where
    Nothing  >>= f = Nothing
    (Just x) >>= f = f x
    return         = Just

いったん、MaybeMonad クラスのインスタンス であると定義してしまえば、複雑な計算を構成するのに、標準モナド演算子を利 用することができます。

-- 複雑な並びを構成するのにモナド演算子が使えます
maternalGrandfather :: Sheep -> Maybe Sheep
maternalGrandfather s = (return s) >>= mother >>= father

fathersMaternalGrandmother :: Sheep -> Maybe Sheep
fathersMaternalGrandmother s = (return s) >>= father >>= mother >>= mother 

Haskellでは、MaybeMonad クラスのインスタンス として標準プレリュードのなかで定義されてます。それゆえ、自分自身で 定義する必要はありません。この後見る別のモナド、すなわち、リスト構築子 も標準プレリュード中で、Monad クラスのインスタンスとして 定義されています。

モナドに作用する関数を書くときには、特定のモナドインスタンスではなく、 Monad クラスを使うようにしましょう。

doSomething :: (Monad m) => a -> m b
という関数のほうが、
doSomething :: a -> Maybe b
という関数よりもはるかに柔軟性があります。 前者は、多くのモナドの型について、対象となるモナドに埋め込まれた 戦略に依存する別々の振舞いを扱うことができますが、後者は厳密に、 Maybe モナド戦略でしか扱えません。

do 記法

標準のモナド関数名を使うことはよいことですが、 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: モナド則