<   2018年 11月 ( 17 )   > この月の画像一覧

素因数分解プログラムを print デバッグ

以前の記事で素因数分解を State モナドで記述したものを掲載した。再掲すると次のようになる。

import Control.Monad.State

primes = sieve [2..]
    where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]

factor x = head $ dropWhile (\y -> x `mod` y /= 0) primes

mfactorize' :: Int -> State [Int] Int
mfactorize' 1 = return 0
mfactorize' x =
    let y = factor x
    in do
        zs <- get
        put (y:zs)
        do mfactorize' (x `div` y)

mfactorize x = execState (mfactorize' x) []

このプログラムで再帰関数 mafactorize’ を print デバッグできるようにしてみた。変更箇所は mfactorize' の型を StateT に変更するのと、mfactorize' のモナドプログラムの中に lift $ print x の一行を挿入したのと、mfactorize の execState を execStateT に変えただけだ。それは次のようになる。

import Control.Monad.State

primes = sieve [2..]
    where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]

factor x = head $ dropWhile (\y -> x `mod` y /= 0) primes

mfactorize' :: Int -> StateT [Int] IO Int
mfactorize' 1 = return 0
mfactorize' x =
    let y = factor x
    in do
        zs <- get
        lift $ print y
        put (y:zs)
        do mfactorize' (x `div` y)

mfactorize x = execStateT (mfactorize' x) []

だいぶエラー・メッセージがでるだろうと覚悟していたら、一発で次のように動いてしまった。

Prelude> :l factorizeT.hs
[1 of 1] Compiling Main ( factorizeT.hs, interpreted )
Ok, modules loaded: Main.
*Main> mfactorize 134
2
67
[67,2]

びっくりしたので報告。


[PR]
by tnomura9 | 2018-11-25 01:03 | Haskell | Comments(0)

いろんなモナドを lift してみた。

StateT モナド変換子でいろいろなモナドを lift してみた。

まずは IO モナド

Prelude Control.Monad.State> runStateT (do x <- get; lift $ print x; return x) "hello"
"hello"
("hello","hello")

次に Maybe モナド

Prelude Control.Monad.State> runStateT (do x <- get; y <- lift $ (Just ", world"); return (x ++ y)) "hello"
Just ("hello, world","hello")

さらに、List モナド

Prelude Control.Monad.State> runStateT (do x <- get; y <- lift [1,2,3]; return (x+y)) 5
[(6,5),(7,5),(8,5)]

これでなんとなくモナド変換子の使い方がわかる。最初は runStateT の第1引数に渡すのは、原則として State モナドのプログラムであること。do 構文、get、return などは State モナドの関数だ。lift がなければ上のプログラムは単なる State モナドのプログラムになる。

Prelude Control.Monad.State> runStateT (do x <- get; y <- return 5; return (x+y)) 3
(8,3)

第2は lift は State モナドとは違うモナドにとっての return であること。次の2つのプログラムを比べるとよくわかる。また、このことによって、lift を使うことで別のモナドを State モナドの中で使うことができるのがわかる。

Prelude Control.Monad.State> runStateT (do x <- return 2; return (x*x)) ()
(4,())
Prelude Control.Monad.State> runStateT (do x <- lift (Just 2); return (x*x)) ()
Just (4,())

第3がどうしてこのような仕様になっているのかわからないが、State モナドのプログラムに他のモナドのプログラムを lift で利用すると、runStateT の値が State モナドではない方のモナド値になってしまうことだ。

State モナドのプログラムの中に lift で IO モナドを利用するとそのままだと純粋関数の世界が IO の値で汚染されてしまう。runState の値が IO モナドになれば、そのような懸念はなくなるのでわかるような気もするが、不思議な仕様だ。ひょっとしたらモナド変換子という名前の意味が State モナドを他のモナドに変換するという意味なのかもしれない。

こうやって、StateT モナド変換子に lift 関数で色々なモナドを与えてみると、モナド変換子の動作がなんとなく理解できる気がしてきた。


[PR]
by tnomura9 | 2018-11-24 22:17 | Haskell | Comments(0)

代数的データ型の再帰的定義

Haskellの代数的データ型は再帰的定義ができる。次のデータ型 Tree a は簡単な2分木の構造を定義できる。

Prelude> data Tree a = Leaf a | Node (Tree a) (Tree a) deriving Show

Prelude> sample = Node (Leaf 1) (Node (Leaf 2) (Leaf 3))
Prelude> sample
Node (Leaf 1) (Node (Leaf 2) (Leaf 3))

これだけなら何ということはないのだが、代数的データ型自身が再帰的構造を持っているため、枝のデータのシリアライズをする関数が次のように簡潔に定義できてしまう。

Prelude> {serialize (Leaf x) = [x]; serialize (Node x y) = serialize x ++ serialize y}
Prelude> serialize sample
[1,2,3]

Haskell はループも再帰関数で書かないといけないと不満に思っていても、このように木構造を扱う関数が簡潔に定義できてしまうのをみたら、再帰関数の威力に驚かされるばかりだ。

もう一つ例をあげてみよう。整数の加算と乗算を行う構文木のデータを次のように定義してみる。

Prelude> data Expr = Lit Int | Add Expr Expr | Mult Expr Expr deriving Show
Prelude> expression = Mult (Add (Lit 1) (Lit 2)) (Add (Lit 3) (Lit 4))
Prelude> expression
Mult (Add (Lit 1) (Lit 2)) (Add (Lit 3) (Lit 4))

この構文木を評価する関数 evaluate は次のように data 宣言の定義をほとんどなぞる形で定義できる。

Prelude> {evaluate (Lit n) = n; evaluate (Add e1 e2) = evaluate e1 + evaluate e2; evaluate (Mult e1 e2) = evaluate e1 * evaluate e2}
Prelude> evaluate expression
21

あとは、パーサを書いて構文木を作成するだけだ。そのパーサも Haskell なら Parsec を用いて簡単に書くことができる。(実際には、書き方を忘れたのでまた再々入門しなくてはならないのだけれど。)

Haskell の再帰関数を書くことで再帰に慣れるといろいろと面白いことができるかもしれない。


[PR]
by tnomura9 | 2018-11-20 21:39 | Haskell | Comments(0)

liftM と lift

liftM はモナドの関数、lift はモナド変換子の関数だ。何かを持ち上げるという意味なのだろうが使い方がさっぱりわからない。こんなときは、単純な使い方を試してみるに限る。liftM と lift を試して見るためには、Control.Monad.State モジュールをインポートすると両方使える。

Prelude> import Control.Monad.State

まず liftM からだ。最初は関数の型を見てみる。

Prelude Control.Monad.State> :t liftM
liftM :: Monad m => (a1 -> r) -> m a1 -> m r

m はモナドを表すので、これは a1 -> r 型の関数から m a1 -> m r 型の関数に変換されることがわかる。そこで、次のように foo という関数を作って試してみた。

Prelude Control.Monad.State> foo = liftM (*2)
Prelude Control.Monad.State> :t foo
foo :: (Monad m, Num r) => m r -> m r

(*2) は数を2倍にする関数だから foo = liftM (*2) は m x の x の値を2倍にして m 2x を返す関数になる。

Prelude Control.Monad.State> foo (Just 2)
Just 4

liftM の動作は意外と単純だった。こんどは lift に挑戦してみる。lift の説明はいろいろ読んでもよくわからなかったので、最も単純な例を試してみた。runStateT の引数に普通にモナドプログラムを渡すと、単に State モナドとして働く。

Prelude Control.Monad.State> runStateT (return 2) ()
(2,())

ところが、lift に Just 2 というMaybe モナドを渡すと、(2,()) というrunState のペアが Just にラッピングされて返されてくる。

Prelude Control.Monad.State> runStateT (lift (Just 2)) ()
Just (2,())

State モナドの中に Maybe モナドの値を放り込んだら、裏表が逆になって Just にラッピングされて戻されてきた。なんだかよくわからないが、ともかくState モナドのプログラムの中で Maybe モナドが使えることがわかる。そこで、もう少し State モナドらしいことをさせてみよう。

Prelude Control.Monad.State> runStateT (do x <- get; y <- lift (Just 3); return (x+y)) 7
Just (10,7)

状態の 7 と Just 3 の 3 が足し算されて値として戻されているが、そのペア (10, 7) は Just にラッピングされて戻ってくる。State モナドのプログラミングのなかで Maybe モナドの Just 3 の 3 を利用することができるが、その場合出力が Just (10,7) のように Maybe モナドになってしまっている。

しかし、lift の引数が IO モナドだとそのありがたみがもっとわかる。

Prelude Control.Monad.State> runStateT (do x <- get; y <- lift(getLine); return (x ++ y)) "hello, "
world
("hello, world","hello, ")

出力にモナドのデータコンストラクタが表示されていないがこれは出力が IO モナドだからだ。

Prelude Control.Monad.State> :t runStateT (do x <- get; y <- lift(getLine); return (x ++ y)) "hello, "
runStateT (do x <- get; y <- lift(getLine); return (x ++ y)) "hello, "
:: IO ([Char], [Char])

このことは、StateTモナド変換子を使うことで、IOモナドのプログラムに状態を導入することができることを示している。つまり、2つのモナドを一緒に使うことができるのだ。これで StateT モナドの使い方がわかったわけではないが、少なくとも IO モナドのプログラムの中に State モナドによる状態を導入できることがわかったので、StateT モナド変換子をいろいろ試してみることができる気がしてきた。


[PR]
by tnomura9 | 2018-11-20 00:03 | Haskell | Comments(0)

snoc

Graham Hutton の Programming in Haskell に snoc 関数の説明が出ていた。定義は次のようになる。

Prelude> snoc x xs = xs ++ [x]

これは cons 演算子の逆になる、cons 演算子は次のようにリストの先頭に要素を追加するが、

Prelude> 1:[2,3]
[1,2,3]

snoc 演算子はリストの後ろに要素を追加する。

Prelude> snoc 3 [1,2]
[1,2,3]

何ということはない関数だが、これを foldr の引数にするとリストの逆順を生成する reverse 関数と同じ動作になる。

Prelude> foldr snoc [] [1,2,3,4,5]
[5,4,3,2,1]

魔法みたいだが、foldr を手書きで展開してみるとその意味がわかる。

foldr snoc [] [1,2,3]
= 1 snoc (2 snoc (3 snoc [])))
= 1 snoc (2 sooc ([] ++ [3]))
= 1 snoc (2 snoc [3])
= 1 snoc ([3] ++ [2])
= 1 snoc [3,2]
= [3,2] ++ [1]
= [3,2,1]

直感的には驚くような魔法に感じるが、手作業で関数の結果を展開していくと、その仕組みは納得できる。簡潔すぎるプログラムが驚くような値を返すのが Haskell の魅力だが、自分の手で関数を展開してみると魔法でも何でもないのがわかる。

また、Haskell のプログラムでは重要なのは値の内容ではなく、式の展開という形式であることがわかる。Haskell のプログラムに馴染む一番の方法は手作業で関数を展開して、Haskell のプログラムにおける形式の重要さに気づくことだ。

[PR]
by tnomura9 | 2018-11-18 09:51 | Haskell | Comments(0)

モナドの制御構造

モナドとは一口に言うと、M a 型のデータを >>= 演算子を介して a -> M b 型の関数にコンテナの値を与え、M b 値の戻り値を返す仕組みだ。そうして、モナドのプログラムとは M a >>= f >>= g >>= ... >>= l というような M a 型のデータを次々に a -> M b 型の関数に渡していく >>= 演算子の連鎖にほかならない。

これはどういうことかと言うと、モナドのプログラムとは非常に限定された関数、すなわち a -> M b 型の関数のみで作成されたプログラムだということだ。例えば次のような do 記法に現れる各行の関数は全て a -> Mb 型の関数である。

Prelude> :{
Prelude| do
Prelude| x <- getLine
Prelude| putStrLn x
Prelude| :}
hello, world
hello, world

これは、関数が逐次処理されていくような逐次実行型のプログラムの場合は扱いが簡単だ。単に、各業の関数を a -> M b 型の関数で記述すればいいだけだからだ。

しかし、プログラムには制御構造が必須の要素だ。制御構造を a -> M b 型の関数のなかに持ち込めなければプログラム言語としてはあまり使い勝手が良くない。

そこで、モナドプログラミングに制御構造を取り込むにはどうすればいいかを考えてみた。

まずは if ~ then ~ else だこれは次のようにそのままモナドのプログラムに取り入れることができる。

Prelude> foo x = if x == 1 then return "one" else return "other"
Prelude> foo 1
"one"
Prelude> foo 2
"other"
Prelude> :t foo 2
foo 2 :: Monad m => m [Char]

ただし、モナドのプログラムの場合値は必ず return で返さなければならない。この、値を最後に return で返すというのが、モナドプログラミングの肝だ。

また、分岐のあとの a -> M b 型関数が複数並ぶような場合はそれを do 記法で一つの関数にまとめてしまわなければならない。たとえば次のようなプログラムは、分岐のあとの複数の操作を do 記法で一つの関数にまとめている。

Prelude> bar x = if x == 1 then do print "One"; return 1 else do print "Other"; return x
Prelude> bar 2
"Other"
2

do 記法というのは a -> M b 型の複数の関数を一つの関数に纏め上げているのだというイメージがこの場合便利だ。

それでは、再帰関数はどうだろうか、関数型のプログラムは繰り返しは再帰関数で記述する。再帰関数が記述できなければモナドプログラミングは応用範囲がひどく狭いだろう。そこで、モナドプログラミングの再帰関数を書いてみた。

Prelude> {fact 0 = return 1; fact n = do m <- fact (n-1); return (n * m)}
Prelude> fact 5
120

このプログラムの要は fact (n-1) の M a 値を一旦 <- 演算子で取り出してから、戻り値として n * m を返すというひと手間をかけているところだ。次の普通の再帰関数の場合と比べてみるとこの工夫の意味がよくわかる。

Prelude> {factorial 0 = 1; factorial n = n * factorial (n-1)}
Prelude> factorial 5
120

fact (n-1) 関数の戻り地は M a 型なのでコンテナの a を取り出すのに x <- fact (n-1) という <- 演算子を使った操作が必要なのだ。

ちなみに、この fact 関数は State モナドにもそのまま使うことができる。

Prelude> fact n = if n == 0 then return 1 else do m <- fact (n-1); return (n*m)
Prelude> import Control.Monad.State
Prelude Control.Monad.State> runState (fact 5) ()
(120,())

リストモナドにも使える。

Prelude Control.Monad.State> fact 5 :: [Int]
[120]

このことは、ここで述べているモナドの制御構造がいろいろなモナドについて普遍的であることを示している。

仕上げとして、コンソールから文字列を入力し、文字列のリストを作るプログラム getLines を書いてみた。getLines を抜けるには空行を入力する。

Prelude Control.Monad.State> getLines = do cs <- getLine; if cs == "" then return [] else do xs <- getLines; return (cs:xs)
Prelude Control.Monad.State> getLines
hello
world

["hello","world"]

結構便利な関数だが、使っているテクニックは、上に述べた「値を返すときは必ず return 関数を使う」、「分岐の処理は do 記法で一つの関数に纏め上げる」、「値を加工する前は必ず x <- func のように <- 演算子でラッピングされていないコンテナの値を取り出す」という3つのルールを使っているだけだ。この3つのルールに従えば、モナドのプログラムに自由自在に制御構造を取り込むことができる。

[PR]
by tnomura9 | 2018-11-15 21:26 | Haskell | Comments(0)

State モナドと素因数分解

Stateモナドで素因数分解プログラムを作ってみた。まずは素数生成器の primes をプログラムした。

Prelude> primes = sieve [2..] where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]
Prelude> take 10 primes
[2,3,5,7,11,13,17,19,23,29]

次に、整数を割り切る一番小さい素数を求める関数 factor を定義した。

Prelude> factor x = head $ dropWhile (\y -> x `mod` y /= 0) primes
Prelude> factor 35
5

factor で次々に整数を割っていけば素因数分解ができるので、素因数分解をする関数 factorize を作ってみた。末尾再帰にすれば、第2引数を状態代わりに使うことができる。

Prelude> {factorize 1 xs = xs; factorize x xs = let y = factor x in factorize (x `div` y) (y:xs)}
Prelude> factorize 35 []
[7,5]

これで素因数分解のコツがわかったので、State モナドを使った素因数分解プログラムに挑戦してみた。

Prelude Control.Monad.State> {mfactorize :: Int -> State [Int] Int; mfactorize 1 = return 0; mfactorize x = let y = factor x in do z <- get; put (y:z); do mfactorize (x `div` y)}
Prelude Control.Monad.State> runState (mfactorize 215) []
(0,[43,5])

この程度のプログラムなら状態を保存するのにわざわざ State モナドを使わなくても末尾再帰の引数に状態をもたせたほうがわかりやすいプログラムになる。

上のプログラムはコンソールで対話的に開発したので少々見づらい。ファイルに作成して整形してみた。

import Control.Monad.State

primes = sieve [2..]
    where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]

factor x = head $ dropWhile (\y -> x `mod` y /= 0) primes

factorize' 1 xs = xs
factorize' x xs =
    let y = factor x
    in factorize' (x `div` y) (y:xs)

factorize x = factorize' x []

mfactorize' :: Int -> State [Int] Int
mfactorize' 1 = return 0
mfactorize' x =
    let y = factor x
    in do
        zs <- get
        put (y:zs)
        do mfactorize' (x `div` y)

mfactorize x = execState (mfactorize' x) []


[PR]
by tnomura9 | 2018-11-15 01:13 | Haskell | Comments(0)

List モナド

Haskell ではリストもモナドだ。その意味は、リストにも return 関数と >>= 演算子が適用できるということだ。return 関数の型は a -> M a だ。これは引数を一つ取りそれをモナド値にラッピングして返すことを意味している。例えば return 3 は整数 3 をリストにラッピングして [3] を返す。

Prelude Control.Monad.State> return 3 :: [Int]
[3]

このモナド値は >>= 演算子によって a -> [b] 型の関数に渡して処理することができる。

Prelude Control.Monad.State> return 3 >>= \x -> [x*x]
[9]

[x*x] でリストにするのではなく return (x*x) のように return 関数でモナドにラッピングすることもできる。

Prelude Control.Monad.State> return 3 >>= \x -> return (x*x) :: [Int]
[9]

しかし、これでは要素が1つだけのシングルトンリストしか扱えないように思える。複数の要素をもつリストをどのようにして return 関数で返すことができるだろうか。それは : 演算子を利用することで可能になる。

Prelude Control.Monad.State> 1:2:return 3
[1,2,3]

こうして返された複数要素のリストには >>= 演算子を使うことができる。

Prelude Control.Monad.State> 1:2:return 3 >>= \x -> return (x*x)
[1,4,9]

この場合2乗の操作はリストの全ての要素に対して行われる。これは map 関数と同じ動作だ。

[1,2,3] というリスト書式の値は直接に >>= 演算子に渡すことができる。

Prelude Control.Monad.State> [1,2,3] >>= \x -> return (x*x)
[1,4,9]

このように、リストがモナドであるという意味は return 関数と >>= 演算子が使えるということだ。do 記法はこれらの糖衣構文なので次のように使うことができる。

Prelude Control.Monad.State> do x <- [1,2,3]; return (x*x)
[1,4,9]

State モナドにしても、リストモナドにしてもそれがモナドであるという意味は return 関数と >>= 演算子が使えるという以外の意味はない。しかし、これはリストが IO モナドと同じように do 記法を用いてプログラムできることを意味している。この意味は軽いものではない。例えば次のようなプログラムを書くこともできるのだ。

Prelude Control.Monad.State> :{
Prelude Control.Monad.State| do
Prelude Control.Monad.State| x <- [1,2,3]
Prelude Control.Monad.State| y <- [4,5,6]
Prelude Control.Monad.State| return (x,y)
Prelude Control.Monad.State| :}
[(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]

State モナドにしても、リストモナドにしても、それがモナドである限り do 記法でプログラムすることができる。ただ、それがどのような実際的な意味を持つのかはプログラムの実例をみて学ばないといけないが、なかなかそのような例に出会わない。

それは置いておいてもこれらのモナドを共通の観点から操作出るというのはなかなかおもしろい経験だ。

[PR]
by tnomura9 | 2018-11-13 22:00 | Haskell | Comments(0)

State モナドとモナド型関数

モナドのプログラミングにおいて、>>= 演算子の左右の項には厳しい制限がある。つまり >>= の左項は M a 型のモナド値であり、>>= の右項は a -> M a 型の関数でなくてはならない。このあたりの説明をスタックの pop 関数と push 関数を例にとってやってみる。

Prelude> import Control.Monad.State
Prelude Control.Monad.State> :{
Prelude Control.Monad.State| pop :: State [a] a
Prelude Control.Monad.State| pop = state (\s -> (head s, tail s))
Prelude Control.Monad.State| :}
Prelude Control.Monad.State> runState pop [1,2,3]
(1,[2,3])

つぎに、push 関数だが、これはスタックに積む値を引数に取るので型は push :: a -> State [a] a だ。

Prelude Control.Monad.State> :{
Prelude Control.Monad.State| push :: a -> State [a] a
Prelude Control.Monad.State| push x = state (\s -> (x, (x:s)))
Prelude Control.Monad.State| :}
Prelude Control.Monad.State> runState (push 5) [1,2,3]
(5,[5,1,2,3])

この push と pop を使ってスタックの先頭の要素を 10 倍にしてみる

Prelude Control.Monad.State> runState (pop >>= \x -> return (10*x) >>= push) [1,2,3]
(10,[10,2,3])

これは do 記法で記述したほうがわかりやすい。

Prelude Control.Monad.State> runState (do x <- pop; push (x * 10)) [1,2,3]
(10,[10,2,3])

>>= 演算子の右項に置くべき a -> State s a 型の関数の作り方をやってみた。やや技巧的だが、これがわかれば、State モナドを自由に操ることができるようになる。

State モナドのプログラミングでは上に述べた state 関数で関数をラッピングする方法より、return, get, put, modify 関数などを使って do 記法でプログラムする方法もある。むしろ、こちらのほうがわかりやすいかもしれない。

Prelude Control.Monad.State> :{
Prelude Control.Monad.State| pop :: State [a] a
Prelude Control.Monad.State| pop = do xs <- get; put (tail xs); return (head xs)
Prelude Control.Monad.State| :}

Prelude Control.Monad.State> runState pop [1,2,3]
(1,[2,3])

Prelude Control.Monad.State> :{
Prelude Control.Monad.State| push :: a -> State [a] a
Prelude Control.Monad.State| push x = do xs <- get; put (x:xs); return x
Prelude Control.Monad.State| :}

Prelude Control.Monad.State> runState (push 5) [1,2,3]
(5,[5,1,2,3])

Prelude Control.Monad.State> runState (do x <- pop; push (x*10)) [1,2,3]
(10,[10,2,3])

get, put 関数の動作は汎用的なので、State モナドのプログラミングはこちらを使うほうが王道だろう。


[PR]
by tnomura9 | 2018-11-11 20:50 | Haskell | Comments(0)

状態付き計算と State モナド

計算の結果が状態に依存するようなプログラムを考えてみよう。例えばカウンターの値を表示したあとカウンターを一つ増加させる関数 countUp 関数を考えてみる。countUp 関数が呼ばれると現在のカウンターの値が表示されると同時に状態としてのカウンターの値が一つ増加される。

irb(main):014:0> $count = 1
=> 1
irb(main):015:0> def countUp
irb(main):016:1> print $count
irb(main):017:1> $count += 1
irb(main):018:1> end

しかし、Haskell では破壊的代入ができないので、値と状態を同時に返す関数を作る必要がある。

Prelude> countUp s = (s, s+1)

そうして、countUp 関数を呼ぶたびに引数として状態の値を与えなくてはならない。

Prelude> countUp 1
(1,2)
Prelude> countUp 2
(2,3)
Prelude> countUp 3
(3,4)

State モナドを使えばこのような状態付き関数をもっとスマートに表記できる。この countUp 関数の型は次のようになる。

Prelude> :t countUp
countUp :: Num t => t -> (t, t)

これは State モナドのコンテナの s -> (a, s) と同じ形をしている。そこで state 関数を使ってこの countUp 関数を State モナドにラッピングする。

Prelude> import Control.Monad.State
Prelude Control.Monad.State> monadCount = state countUp

すると monad 化した monadCount 関数を使えばいちいち状態を入力しなくてもカウントアップの動作を行うことができる。

Prelude Control.Monad.State> runState (do monadCount; monadCount; monadCount) 1
(3,4)

不審に思うかもしれないが State モナドの >>= 演算子の動作がこれを可能にしている。State モナドでは >>= 演算子は、左辺のモナド値 State s a の a と現在の状態 s を使って右辺のモナド関数で計算を行い、新しい値と状態のペア (a,s) を State モナドにラッピングして戻り値として返す。したがって、暗黙に状態の値が渡されているのだ。monadCount 関数では明示的な引数の入力はなく状態 s の値だけを利用するので、上のように monadCount 関数を呼び出すたびにカウンタが増加する。

IO モナドの >>= 演算子には状態を利用する性質はないので、State モナドの >>= 演算子の動作は State モナド特有のものだ。モナド共通の >>= の性質に加えて State モナドの特有の >>= の性質が加えられている。このように階層的に演算子の性質をもたせることができるのが、Haskell に多くのモナドが存在する理由のひとつだ。

このように State モナドでは s -> (a, s) 型の関数があれば、state 関数で簡単に State モナドでラッピングすることができる。たとえばスタックの pop 関数は次のように定義できる。

Prelude Control.Monad.State> :{
Prelude Control.Monad.State| pop :: State [a] a
Prelude Control.Monad.State| pop = state $ \s -> (head s, tail s)
Prelude Control.Monad.State| :}
Prelude Control.Monad.State> runState (do pop) [1,2,3]
(1,[2,3])

State モナドの本領はこのような領域で発揮されるのではないだろうか。s -> (a,s) 型の状態のある計算の場合は State モナドを使うことを考えたほうがいいかもしれない。


[PR]
by tnomura9 | 2018-11-11 09:25 | Haskell | Comments(0)