7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

モナドについて勉強したことの整理(個別モナド)

Last updated at Posted at 2015-02-09

学習の素材は、
関数プログラミング実践入門

この表記は、私見・感想です。

前段として、モナドについて勉強したことの整理を書いています。

いろいろなモナド

GHC.Base のソースコード

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)として、各アクションが持っているものを合成していくことになる。

Writerモナド
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 のインスタンスでなくてはならない。
ここで、runWriterWriterモナドアクションを走らせる実行器 となる。

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 モナドのアクションは、何らかの状態を保っており、状態値を取り出して参照したり、状態値を変更しているかのような動作をさせることができる。

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 モナドなどと同様に、runStateState モナドアクションを走らせる実行器となる。rerturn a は、s を引数にとって、(a, s) のタプルを返す関数を包んだ型State state a の値を作る。
また、bind >>= は、与えられた状態値で前のアクションの値とそのアクションによって変更された状態を取り出してから、それらを次のアクションに渡す。つまり、状態値を引数にとり、合成されたアクションの値とそれらのアクションによって変更された最終的な状態を返す関数 を包んだモナドアクションを作る。

Stateモナドに用意されたアクション
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加算されていることがわかる

IO

7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?