いろんなMonadをdo構文で
環境
- stackを用いています。stack導入は、http://qiita.com/igrep/items/da1d8df6d40eb001a561 とかをごらんください。
準備
ghciのコマンド
簡単にさらっと。
- 対話的インタプリタ ghciを使用するときは、
stack exec ghci
とすれば、起動します。-
.hs
ファイルをコンパイルする時は:l file.hs
or:load file.hs
でOKです。 - モジュールのロードは
:m +Module
で追加、:m -Module
で除去できます。
-
Note) :reload
で最後にコンパイルしたファイルをコンパイルできます。引数にfile.hs
のように、ファイル名を明示する必要がないことがメリットです。
Monadとは
Haskellのモナドは、型クラスの一種として、ただの計算に文脈を付与する方法と文脈を伴う計算同士の組み合わせ方法を一緒に与えておくことで、どの文脈を持った計算であっても統一された文法で扱える強力な仕組みです。(中略)言語内にHaskellの機能をまるまる継承したDSL(ドメイン記述言語)を組めるようなものです。[2, p.241]
ざっくりとしたイメージとしては、Monadは箱みたいなものです。
箱自体をプレゼントされても困ると思います。それは、箱自体は中身のないものだからです。
しかし、箱を特殊化することにより、
段ボール => 引っ越し
カゴ => 買い物
タンス => 衣装を入れる
みたいに、様々な用途に使えたりします。Monadが「強力な仕組み」というのはそういうことです。Haskellでは、右側の意味づけを'文脈'と言ったりします。
上記の例をMonadに戻してみると
Maybe monad => 失敗可能性
List monad => 非決定性計算
IO monad => 副作用
みたいな感じになります。とは言っても、言葉だけだと「わかったような、わからんような」みたいな状態になるので、以下では、本節で述べたことを頭の片隅に置きながら、コードを読むと良いと思います。
(Monadについてイメージをもっと掴みたい方は、http://qiita.com/kazatsuyu/items/d1c9b97d92af89c4cca0 を見ると幸せになれるはず!)
(>>=)
- (>>=)の定義自体は
(>>=) :: Monad m => m a -> (a -> m b) -> m b
で、関数a -> mb
を>>=
に食わせてやると、m b
型の値が出力されます:
> Just 3 >>= \x -> Just (x*2)
Just 6
return
return :: Monad m => a -> m a
return
は値をMonadの箱に突っ込むだけの関数です。
do構文
よく見かける例
-- getLine :: IO String
-- putStrLn :: String -> IO ()
main :: IO ()
main = do
x <- getLine
putStrLn $ "Hello " ++ x
は
main :: IO ()
main =
getLine >>= \x -> putStrLn $ "Hello " ++ x
と同じです。do構文は(>>=)
(バインド、[1, p.287])のsyntax sugarなのです。
do構文のいいところは、左側の値x :: String
が、あたかもMonadという箱(getLine :: IO String
)の中身を取り出したかのように見える点です。
モナドであれば、IO monadに限らずなんでも使えます([1, p.296]に記載されてる。Maybe monadのdo記法の例は[1, p.297]にある).
上記のJust 3 >>= \x -> Just (x*2)
はdo構文を用いると
calc :: Maybe Int
calc = do
x <- Just 3
return (x*2)
とか、ghci上でワンライナーで書きたいときは、
> do { x <- Just 3; return (x*2) }
と書けば良いです。
Note) ghci上でどうしても複数行を書きたい場合は:{
と:}
により、先頭と末尾を明示します。
Prelude> :{
Prelude| calc = do
Prelude| x <- Just 3
Prelude| return (x*2)
Prelude| :}
Prelude> calc
Just 6
ということで、Maybe, Either, リスト, Writer, Reader, State monadでdo構文を明示的に使った場合とそうでなく、バインド(>>=)そのものを使用した場合をずらっとまとめてみました。
do構文とバインド(>>=)を使った例
Maybe monad
- Maybeの定義
-- Nothingが失敗したという文脈を持っている
data Maybe a = Nothing | Just a
- (>>=)の定義
instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= _ = Nothing
- do構文を用いて
-- calc :: Maybe Int
calc = do
x <- Just 3
y <- Just 5
z <- Nothing
-- return (x + y) -- Just 8
return (x + y + z) -- Nothing
- (>>=)を用いて
> let calc = Just 3 >>= \x -> Just 5 >>= \y -> return (x + y) in calc
8
> let calc = Just 3 >>= \x -> Just 5 >>= \y -> Nothing >>= \z -> return (x + y + z) in calc
Nothing
Note) Just 3 >>= \x -> Just 5 >>= \y -> return (x + y)
は、
Just 3 >>= (\x -> (Just 5 >>= (\y -> return (x + y)) ) )
のように右側から左方向に見ていきます。以下の例でも同様。
Either monad
- Eitherの定義
-- MaybeのNothingはエラー時の情報を持たなかったが、
-- Either型クラスはLeftにエラー情報を付加することができる。
data Either a b = Left a | Right b
- (>>=)の定義
instance Monad (Either e) where
Right m >>= k = k m
Left e >>= _ = Left e
- do構文を用いて
calc :: Either String Int
calc = do
x <- Right 5
y <- Right 3
z <- Left "NG"
-- return (x + y) -- Right 8
return (x + y + z) -- Left "NG"
- (>>=)を用いて
> let calc = Right 5 >>= \x -> Right 3 >>= \y -> return (x + y) in calc
Right 8
> let calc = Right 5 >>= \x -> Right 3 >>= \y -> Left "NG" >>= \z -> return (x + y + z) in calc
Left "NG"
リスト monad
- []の定義
-- 非決定的(優柔不断な)計算ができる
data [] a = [] | a : [a]
- (>>=)の定義
instance Monad [] where
-- xs >>= f = concat $ map f xs ともかける
-- concat :: Foldable t => t [a] -> [a] Foldableをぺしゃんこに平坦化する
-- concat [[2,3],[3,4],[4,5]] で[2,3,3,4,4,5]となる
xs >>= f = [y | x <- xs, y <- f x]
- do構文を用いて
-- calc :: [Int]
calc = do
x <- [1..3]
y <- [1..2]
return (x+y) -- [2,3,3,4,4,5]
- (>>=)を用いて
> let calc = [1..3] >>= \x -> [1..2] >>= \y -> return (x + y) in calc
[2,3,3,4,4,5]
IO monad
Note) https://wiki.haskell.org/IO_inside を参考にしました1。
- IOの定義
-- 副作用を扱える。というより、副作用が混じっている部分をIOモナドに閉じ込めて分離ができる。本節の注釈も参照
-- Haskellが純粋なのに、なぜ副作用を扱えるかのざっくりとした説明は[2, p.281]を見ると良い。
type IO a = RealWorld -> (a, RealWorld)
-- Here is the actual IO definition from the GHC sources:
-- "(# #)" strict tuple for optimization
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
- (>>=)の定義
(>>=) :: IO a -> (a -> IO b) -> IO b
(action1 >>= action2) world0 =
let (a, world1) = action1 world0
(b, world2) = action2 a world1
in (b, world2)
- do構文を用いて
-- getLine :: IO String
-- putStrLn :: String -> IO ()
calc :: IO ()
calc = do
x <- getLine
y <- getLine
putStrLn $ "Hello " ++ x ++ " " ++ y -- $input1<CR>$input2<CR>Hello $input1 $input2
- (>>=)を用いて
> let calc = getLine >>= \x -> getLine >>= \y -> putStrLn $ "Hello " ++ x ++ " " ++ y in calc
hi -- input
good -- input
Hello hi good
Writer monad
- Writerの定義
-- [1]ではこんな感じの定義になっている
-- wの部分が今までの履歴を残すという文脈に。
newtype Writer w a = Writer {runWriter :: (a, w)}
-- import Control.Monad.Writer
-- :info Writerで調べた結果
-- WriterTはモナド変換子(モナドの一種)。モナド変換子については、補足も参照。
-- Writerのさらに抽象化された型クラスWriterTが定義されている。
type Writer w = WriterT w Data.Functor.Identity.Identity :: * -> *
newtype WriterT w (m :: * -> *) a = WriterT {runWriterT :: m (a, w)}
- (>>=)の定義
-- [1]ではこんな感じ
-- タプルの1番目の要素:fが作用する 2番目の要素:結合(Monoid型クラスのmappend関数が使用されている)
instance (Monoid w) => Monad (Writer w) where
(Writer (x, v)) >>= f = let Writer (y, v') = f x in Writer (y, v `mappend` v')
-- http://www.geocities.jp/m_hiroi/func/haskell30.html を参照
instance (Monoid w, Monad m) => Monad (WriterT w m) where
m >>= k = WriterT $ do (y, v) <- runWriterT m
(z, v') <- runWriterT (k y)
return (z, v `mappend` v')
- do構文を用いて
import Control.Monad.Writer
calc :: Writer [String] Int
calc = do
x <- writer (3, ["input : " ++ show 3])
y <- writer (5, ["input : " ++ show 5])
-- WriterT : モナド変換子(モナドの一種)
-- runWriter calc とすれば、(8,["input : 3","input : 5"])
return (x+y) -- WriterT (Identity (8,["input : 3","input : 5"]))
ただし、
-- writer関数
class (Monoid w, Monad m) => MonadWriter w m | m -> w where
writer :: (a,w) -> m a
- (>>=)を用いて
-- :m +Control.Monad.Writer
> let calc = writer (3, ["input : " ++ show 3]) >>= \x -> writer (5, ["input : " ++ show 5]) >>= \y -> return (x + y) :: Writer [String] Int in runWriter calc
(8,["input : 3","input : 5"])
Reader monad
ちょっとした解説
Reader monadは初見では分かりにくいのですが、1変数関数$f_i (i = [1,N) )$として、
-- 例として、N = 3
-- fmap ($ 4) [f1, f2, f3] は [7,20,2] となるので、7+20-2で25となる
> let func opr x = opr $ fmap ($ x) [f1, f2, f3] where {f1 = (+3); f2 = (*5); f3 = (div 8)} in func (\(x1:x2:x3:[]) -> x1+x2-x3) 4
25
というように、作用される値x
を介さず、関数f1, f2, f3
のみからfunc (\(x1:x2:x3:[]) -> x1+x2-x3) :: (Num t, Integral b) => b -> t
みたいな1変数関数が作れるところが特徴です。関数合成f . g
とは異なることに注意してください。
多分一番初めにdo構文の使用例を見るのが一番わかりやすいのではないかと。
- Readerの定義
a. (->) a b
はa -> b
のこと。
> :info (->)
data (->) t1 t2
b. Control.Monad.Readerを用いる場合
-- newtypeについては補足に記載しました。
newtype Reader env a = Reader {runReader :: env -> a}
-- import Control.Monad.Reader
-- :info Readerで調べた結果
-- Reader型クラスのさらに抽象化された型クラスReaderTが定義されている。
type Reader r = ReaderT r Data.Functor.Identity.Identity :: * -> *
-- ReaderT : モナド変換子(モナドの一種)
newtype ReaderT r (m :: k -> *) (a :: k) = ReaderT {runReaderT :: r -> m a}
- (>>=)の定義
a. (->)
(関数作用)そのものが実はMonadなのです。
-- GHC.Base
instance Monad ((->) env) where
f >>= k = \e -> k (f e) e
b. Control.Monad.Readerを用いる場合
-- [3, p.269]
-- f : env -> a (where f x = f_1 x + f_2 x + .. + f_N x)というような関数を作れる
instance Monad (Reader env) where
Reader f >>= g = Reader (\e -> RunReader (g (f e) e)
-- http://www.geocities.jp/m_hiroi/func/haskell30.html を参照
instance Monad m => Monad (ReaderT r m) where
return x = ReaderT $ \_ -> return x
m >>= k = ReaderT $ \r -> do a <- runReaderT m r
runReaderT (k a) r
- do構文を用いて
a. (->)
をそのまま用いた場合
import Data.Char
readInt = read :: String -> Int
-- useless example:(, but this is easy? to grasp what it is:)
calc :: String -> Int
calc = do
x <- (sum . map digitToInt) -- (->) String
y <- readInt -- (->) String
-- x :: Int, y :: Int
return (x+y) -- calc "12" is 15 :: Int
-- (sum . map digitToInt) "12" + readInt "12"という計算をする。
b. Control.Monad.Readerを用いた場合
import Data.Char
import Control.Monad.Reader
readInt = read :: String -> Int
-- same as above!
calc :: Reader String Int
calc = do
-- type String = [Char] なので、両者は同一です
x <- reader (sum . map digitToInt) -- :: MonadReader [Char] m => m Int
y <- reader readInt -- MonadReader String m => m Int
-- x :: Int, y :: Int
return (x+y) -- runReader calc "12" が15となる。
--ReaderTは関数自体を包んでいるので、WriterTのようにそのままではshowできない。
ただし、
class Monad m => MonadReader r m | m -> r where
reader :: (r -> a) -> m a
- (>>=)を用いて
a. (->)
をそのまま用いた場合
-- import Data.Char
> let calc = (sum . map digitToInt) >>= \x -> (read :: String -> Int) >>= \y -> return (x + y) in calc "12"
15
b. Control.Monad.Readerを用いた場合
-- import Data.Char
-- import Control.Monad.Reader
> let calc = reader (sum . map digitToInt) >>= \x -> reader (read :: String -> Int) >>= \y -> return (x + y) :: Reader String Int in runReader calc "12"
State monad
haskellは純粋なので、参照透過性(いつ、どの部分で実行しても、常に同じ結果を返す性質)が担保されます。つまり、変数を書き換えたりはできません。でも、「状態」が時間や状況によって変わってしまう場合も時にはあります(マルコフ連鎖とかはまさにそういう感じだよね)。
その時は過去の履歴に応じて、次の行動が決まるわけで、State monadを用いることで、必要となる過去の履歴という状態を保存しつつ、求めたい値も導出できる。どういうことかというと、Stateの定義から見ていくのが手っ取り早いです。
- Stateの定義
-- Reader Monadと同じく関数を包んでいる形。
-- (a, s)の a : 求めるべき値 , s : 状態(現在の値と過去の履歴)
newtype State s a = State {runState :: s -> (a, s)}
-- import Control.Monad.State
-- :info Stateで調べた結果
-- StateTはモナド変換子(モナドの一種)。モナド変換子については、補足も参照。
-- Stateのさらに抽象化された型クラスStateTが定義されている。
type State s = StateT s Data.Functor.Identity.Identity :: * -> *
newtype StateT s (m :: * -> *) a = StateT {runStateT :: s -> m (a, s)}
- (>>=)の定義
-- [1]ではこんな感じ
-- タプルの1番目の要素:gが作用する 2番目の要素:prevの状態s1が保存されてる)
instance Monad (State s) where
(State f) >>= g = State $ \s -> let (x, s1) = f s in runState (g x) s1
-- http://www.geocities.jp/m_hiroi/func/haskell30.html を参照
instance Monad m => Monad (StateT s m) where
m >>= k = StateT $ \s -> do (a, s') <- runStateT m s runStateT (k a) s'
- do構文を用いて
import Control.Monad.State
-- [Int]は状態(この例だと、スタック)
-- Intは出力値
calc :: State [Int] Int
calc = do
state $ \xs -> ((), 10:xs) -- push 10
a <- state $ \(x:xs) -> (x, xs) -- a <- pop
b <- state $ \(x:ys) -> (x, ys) -- b <- pop
return (a+b) -- runState calc [5,4..1] として、(15,[4,3,2,1])を得る。
ただし、
class Monad m => MonadState s (m :: * -> *) | m -> s where
state :: (s -> (a, s)) -> m a
pop関数、push関数を作ってしまうと、分かりやすい:
import Control.Monad.State
pop :: State [Int] Int
pop = state $ \(x:xs) -> (x, xs)
push :: Int -> State [Int] ()
push a = state $ \xs -> ((), a:xs)
calc :: State [Int] Int
calc = do
push 10
a <- pop
b <- pop
return (a+b) -- runState calc [5,4..1] として、(15,[4,3,2,1])を得る。
- (>>=)を用いて
-- :m +Control.Monad.State
-- popとpushは事前に上記のように定義しておきます。
> let calc = push 10 >> pop >>= \a -> pop >>= \b -> return (a + b) in runState calc [5,4..1]
もちろん、Monadはほかにもいろいろありますが、とりあえず [1] などのHaskell本に記載されているMonadは網羅しました。
補足
newtype
データを包むnewtypeとMonadは相性が良いです(上記の例だと、Writer, Reader, Stateモナド)。newtypeの挙動について簡易的ですがまとめておきます。
> newtype Tup a = Tup { getTuple :: (a, a) } deriving (Show)
> let x = Tup (6,9) -- setするときは(a, a)として直接突っ込めば良い
> x
Tup {getTuple = (6,9)}
-- getTuple :: Tup a -> (a, a)
> getTuple x -- getするときは x :: Tup型を引数に取れば良い。
(6,9)
monad 変換子
https://github.com/Kinokkory/wiwinwlh-jp/wiki/モナド変換子
あたりがわかりやすかったです2。
[2, p.261]にもわかりやすい記載があります。モナド変換子はあるモナドを別の新しいモナドに進化させることのできる(文脈を合成することができる)モナドです3。モナド変換子はWriterT, ReaderT, StateT
とすでに出てきましたが、自明なモナドIdentity
を定義することで、モナド変換子さえ用意すれば、今回扱うモナドのWriter, Reader, State
が作れます。