top page > computer > haskell > web_lecture > for_programmer > monad_cage.html
更新日:
文責: 重城良国

モナド: ライオンの檻

(工事中 60%)

Lion.hs

はじめに

モナド関数の型は

return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b

のようになっている。モナドのインターフェースがモナド関数のみだったとする。すると一度文脈がついた値からは文脈を外すことはできない。(a -> m b)型の関数によって文脈のつかない値を取り出して使うことはできるが、取り出された値は必ず再び文脈のついた値として返される。

この性質によって安全にライオンの世話をしてみよう。

モジュール

Haskellでは値に対するアクセスの制御にはモジュールを使う。モジュール名はLionとする。ファイル名はモジュール名に.hsをつけた名前にするといい。ここではLion.hsとする。ソースファイルの先頭に

{-# LANGUAGE MonadComprehensions #-}

module Lion where

としておこう。

GHC-7.8だとimport Control.Applicativeが必要だが7.10以降なら不要と思われる。あとできちんと確認すること!

ライオン

ライオンは名前と状態を持つものとする。名前はStringとし状態はHungry, Normal, Fullの3つをとるものとする。

data Lion = Lion Name State deriving Show

type Name = String
data State = Hungry | Normal | Full deriving show

値構築子Cagedのついた値は檻に入っていることにする。

newtype Caged a = Caged a deriving Show

ボイラープレート

型クラスFunctor, Applicativeのインスタンス宣言はモナドに対してはボイラープレートとなる。

instance Functor Caged where
fmap = (=<<) . (return .)

instance Applicative Caged where
pure = return
mf <*> mx = [ f x | f <- mf, x <- mx ]

モナド

このモナドは今まで見てきたなかで一番単純なものとなる。関数returnは単にそのまま「檻に入れる」だけだし、演算子(>>=)は値をとりだして関数を適用するだけだ。

instance Monad Caged where
return = Caged
Caged x >>= f = f x

ライオンを生み出す

ライオンは檻のなかで生まれる。

lion :: Name -> Caged Lion
lion n = Caged $ Lion n Hungry

ライオンの世話をする

ライオンの状態は餌をあげるとHungryからNormalに、NormalからFullになる。遊んであげるとFullからNormalに、NormalからHungryになる。

feed, play :: Lion -> Lion
feed (Lion n Hungry) = Lion n Normal
feed (Lion n _) = Lion n Full

play (Lion n Full) = Lion n Normal
play (Lion n _) = Lion n Hungry

エクスポートリスト

module Lion whereの行を

module Lion (Lion, Caged, lion, feed play) where

としよう。型LionやCagedはエクスポートしているが、値構築子LionやCagedはエクスポートしていないことに注意しよう。

檻の外にライオンが?!

これでライオンが檻の外に出る心配はない。

% ghci Lion.hs
*Lion> Lion "denger" Hungry
Lion "denger" Hungry

危ない!ライオンが檻の外にいる。対話環境のモジュール名Lionの左に*がある。*Lionは「モジュールLionのなかにいるよ」という意味だ。エクスポートしていない値も使うことができる。モジュールLionの外からこのモジュールをインポートしている状態にしよう。

*Lion> :m Lion
Prelude Lion> Lion "denger" Hungry
<interactive>:X:Y: Not in scope: data constructor `Lion'
<interactive>:X:Y: Not in scope: data constructor `Hungry'

これでライオンが檻の外をうろつくことはなくなった。ライオンが生まれるよ!

Prelude Lion> lion "Simba"
Caged (Lion "Simba" Hungry)
Prelude Lion> let simba = it

必ず檻にもどす

餌をあげてみよう。

Prelude Lion> feed simba
(型エラーの詳細はあとで書く)
Prelude Lion> simba >>= feed
(型エラーの詳細はあとで書く)

型エラーとなる。必ずreturnで檻にもどす必要がある。

Prelude Lion> simba >>= return . feed
Caged (Lion "Simba" Normal)
Prelude Lion> it >>= return . feed
Caged (Lion "Simba" Full)
Prelude Lion> it >>= return . play
Caged (Lion "Simba" Normal)
Prelude Lion> it >>= return . play
Caged (Lion "Simba" Hungry)

まとめ

値構築子をエクスポートせずにモナドインターフェースだけを公開することで一度文脈のなかにいれた値の文脈を外せないようにできる。

「モナド: 計算のログ」へもどる 「モナド: まとめ」へ

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics