学習の素材は、
関数プログラミング実践入門
この表記は、私見・感想です。
前段として、モナドについて勉強したことの整理を書いています。
いろいろなモナド
Identity
Identity モナドが意味する文脈はない。つまり、文脈をもたないモナドがIdentity
となる。
Maybe
Maybeモナドが持つ文脈は、 失敗の可能性 である。Maybeモナドのアクションは、失敗する可能性がある、と捉えることができる。
instance Monad Maybe where
return = Just
Just x >>= f = f x
Nothing >>= _ = Nothing -- Nothing にアクションを結合しても答えはNothing
リスト
リストモナドが意味する文脈は、 複数の可能性 である。リストモナドのアクションは、複数の結果を取り得ると見ることができる。
instance Monad [] where
return = (:[]) -- ただ一つだけ要素を持つリスト
-- (>>=) = concatMap -- 2015-3-23 当記事の筆者の考えで訂正
(>>=) = flip concatMap -- concatMapは (a -> [b]) -> [a] -> [b] となっていて引数の順序が逆だったため
または
m >>= k = foldr ((++) . k) [] m -- GHC.Base の定義
それぞれのアクションがリストを返したとしても、最終的な返り値は一つのリスト形式となる。
複数の結果を返すかもしれないアクションが連続的に処理されると、複数の結果のカタマリが複数個できてしまいそうだが、このモナドのおかげで最終的に1つのリストに連結される。
リスト内包表記
リストモナドは、リスト内包表記 (list comprehension)という構文糖衣を備えている。数学の集合を表す式のように書けることが特徴。
-- 素数を表すリスト(通常の記載方法)
primes :: [Integer]
primes = f [2..] where
f (p:ns) = p : f (filter((/=0) . (`mod` p)) ns
-- -- 素数を表すリスト(リスト内包表記)
primes :: [Integer]
primes = f [2..] where
f (p:ns) = p : f[ n | n <- ns, n `mod` p /= 0 ]
Reader
Readerモナドが持つ文脈は、 参照できる環境の共有 である。このアクションは、暗黙のうちに同じ環境値を受け渡し、必要な時にはその環境値を取り出して使うことができる。
newtype Reader env a = Reader { runReader :: env -> a }
instance Monad (Reader env) where
return a = Reader (\e -> a)
Reader f >>= g = Reader (\e -> runReader (g (f e)) e) -- g .f の値を取り出す
Reader env
までが、モナドとなる。
ここで、runReader
はReaderモナドのアクションを走らせるための モナドの実行器 でもある。runReader
を使うと、様々な処理が結合された結果としてのReaderモナドの中身を取り出すことができる。
runReader は、
env -> a
型の関数が格納されたReaderモナドのフィールド。これに環境値を引き渡すと、文脈を除いた値を取り出すことができる。
bind演算子は、最初のアクションの結果を次のアクションの引数として渡し(Reader env a
型の値が返る )、runReaderを使ってその値を得るための関数を取得し、それをReaderコンストラクタで包んでいる。つまり、アクションが合成されている。(ちょっとややこしいが、そういうこと)
import Control.Monad.Reader
data Config = Config { verbose :: Int
, debug :: Bool
}
configToLevel :: Config -> Int
configToLevel config
| debug config = 10
| otherwise = verbose config
outputLevel :: Reader Config [Int]
outputLevel = do
config <- ask
return [1 .. configToLevel config]
output :: Int -> String -> Reader Config (Maybe String)
output level str = do
ls <- outputLevel
return (if level `elem` ls then Just str else Nothing)
*ConfigReader> runReader (output 1 "test") (Config 1 False)
Just "test"
*ConfigReader> runReader (output 2 "test") (Config 1 False)
Nothing
*ConfigReader> runReader (output 2 "test") (Config 1 True)
Just "test"
Writer
Writer モナドが持つ文脈は、 主要な計算の横で、別の値も一直線に合成する というものである。ここでいう別の値は 余分な結果 (extra)として、各アクションが持っているものを合成していくことになる。
new type Writer extra a = Writer { runWriter :: (a, extra) }
instance Monoid extra => Monad (Writer extra) where
return a = Writer (a, empty)
Writer (a, e) >>= f = let (b, e') = runWriter (f a) in Writer (b, e `mappend` e')
Writer extra
までがモナドとなる。型制約により、extra
は、型クラス Monoid
のインスタンスでなくてはならない。
ここで、runWriter
は Writerモナドアクションを走らせる実行器 となる。
runWriter
は、(a, extra)
型のタプルが格納されたWriter
モナドのフィールド。これに合成されたWriter
モナドを引き渡すと、計算が連鎖的に行われると同時に、extra
の中身は追記されて、最終的に(a, extra)の形式で情報が出力される。
import Control.Monad.Writer
-- sをログとして記録する
logging :: String -> Writer [String] ()
logging s = tell [s]
-- n番目のフィボナッチ数を呼び出しログ付きで計算する
fibWithLog :: Int -> Writer [String] Int
fibWithLog n = do
logging ("fibWithLog " ++ show n)
case n of
0 -> return 1
1 -> return 1
n -> do
a <- fibWithLog (n-2)
b <- fibWithLog (n-1)
return (a+b)
*LogWriter> runWriter (fibWithLog 3)
(3,["fibWithLog 3","fibWithLog 1","fibWithLog 2","fibWithLog 0","fibWithLog 1"])
*LogWriter>
tell関数の引数( = logging 関数の引数 ) が、bind(
<-
) されるたびに追記されて出力されていることがわかる。
State
State
モナドが持つ文脈は、 状態の引き継ぎ である。State
モナドのアクションは、何らかの状態を保っており、状態値を取り出して参照したり、状態値を変更しているかのような動作をさせることができる。
newtype State state a = State { runState :: (state -> (a, state))}
instance Monad State state where
return a = State (\s -> (a, s))
State x >>= f = State (\s -> let (a, s') = x s in runState (f a) s') -- State (\s -> ((f a), s'')
Reader
モナドなどと同様に、runState
はState
モナドアクションを走らせる実行器となる。rerturn a
は、s
を引数にとって、(a, s)
のタプルを返す関数を包んだ型State state a
の値を作る。
また、bind>>=
は、与えられた状態値で前のアクションの値とそのアクションによって変更された状態を取り出してから、それらを次のアクションに渡す。つまり、状態値を引数にとり、合成されたアクションの値とそれらのアクションによって変更された最終的な状態を返す関数 を包んだモナドアクションを作る。
get :: State state state
get = State (\s -> (s, s))
put :: state -> State state ()
put = State (\s -> (_, s)
modify :: (state -> state) -> State state ()
modify f= get >>= put . f
gets :: (state -> a ) -> State state a
gets f = get >>= return . f
-- 状態値を表すリストの先頭要素に何らかの変換を適用する
import Control.Monad.State
push :: a -> State [a] ()
push = modify . (:) -- modify ([a] -> [a]) となり、 push x の結果、状態値が (x : xs) となる
pop :: State [a] a
pop = do
value <- gets head
modify tail
return value -- modify tail により、先頭要素を除いたリストが状態値となってStateモナドアクションに包まれる
applyTop :: (a -> a) -> State [a] ()
applyTop f = do
a <- pop
push (f a)
*Main> runState (applyTop (+10)) [0..9]
((),[10,1,2,3,4,5,6,7,8,9])
先頭要素がセクション
(+10)
により10加算されていることがわかる