モナドの話
アクションの話
アクションとラムダの話
doの実体はbind.hs
doで何行も書かれているのは、bindで一行でまとめることが出来る。
main = print "hello" >>= \_ -> print "world"
main = print "abc" >>= \_ -> print "def" >>= \_ -> print "ghi" >>= \_ -> print "jkl" >>= \_ -> print "mno"
最後のは捨てることは出来ない。
printは引数で、副作用でコンソール出力している。
なお、ポイントフリースタイル的なことは出来る。
main = return "hello" >>= print
doを使わなくても同じ処理を書くことができます。ってことが言えます。
ラムダ式やセクションを使って、式の一部を省略出来る。
import System.Random import Data.IORef main = do a <- newIORef =<< (getStdRandom $ randomR (0, 9) :: IO Int) -- a' <- readIORef a -- writeIORef a (a' + 1) -- 上2行の代わり modifyIORef a (+ 1) print =<< readIORef a
IOモナド
今まで出てきたモナドはIOモナドで、IOモナドによるアクションはIOアクションという。
IOモナドの中に参照透過性が保証されない特殊な関数が隠されている。 で、この特殊な関数は取り出すことが出来る。
処理系依存なので、以下のプログラムは通常は使わない。
import GHC.Base hello1 = unIO $ return 1
IOモナドは処理系依存の特殊関数を利用して値を格納している。関数や値を取り出したり格納するのにunIOとかIOとかreturnとか<-とかが使える。
IOモナドの中に関数が入っていることはプログラマは意識しなくていい。 こういう手法はオブジェクト指向のデザインパターン、ファクトリメソッドに似ている。
アンボックス化タプル
- タプルと違って計算結果が含まれる。式を入れるとそこから計算をしないといけないから効率が悪い。
vs 非ボックス化 人によってはアンボックスだったり非ボックスだったりする。
- 戻り値は2要素のアンボックス化タプル
- 第1要素は引数を素通ししたもの
- 第2要素は今まで「アクションから取り出した値」と言っていた値
- アンボックス化タプルを使うことで、タプルに格納する際に式が評価される
アンボックス化タプルが使用された瞬間に計算が終わっている。
IOモナド内の関数
s→s1→s2→s3で、世界が次々と変わっていく。状態変化。
{-# LANGUAGE UnboxedTuples, MagicHash #-} import GHC.Base main' :: State# RealWorld -> (# State# RealWorld, () #) main' s = let (# s1, _ #) = unIO (print "hello") s (# s2, _ #) = unIO (print "world") s1 (# s3, r #) = unIO (print "!!") s2 in (# s3, r #) main :: IO () main = IO main'
なお、この書き方はIOモナドのみの書き方。 状態の受け渡しをモナドでかくしている。
リストとIOUArrayは副作用の点で違うものですよ、っていう話。 qiita.com
IOUArrayは状態が変化している。 通常、過去の状態を弄ることは出来ない。
Clean
Haskellと似ている言語で、副作用の表現方法がHaskellではモナドなのに対し、Cleanでは一意型を使う。
- 一意型とは:一意性を持つ型
- 一意性とは:一度しか使われないこと
(一度しか使ってはいけない型のことを一意性の型)
Cleanはググラビリティが低い…
{-# LANGUAGE UnboxedTuples #-} import GHC.Base test1 = do a <- return 1 print a test2 = return 2 >>= \a -> print a test3 = IO $ \s -> let (# s1, a #) = unIO (return 3) s (# s2, r #) = unIO (print a) s1 in (# s2, r #) main = test1 >> test2 >> test3
- IOモナドの中には関数
- 関数は状態を受け取って、更新された状態と値を返す
- 状態の受け渡しを隠せば、IOモナドから値だけを取り出しているように見える
- 状態をバケツリレーすることでコンテキストが構成されます。
- doブロックは独自のコンテキストを持つと見なせます。
- このモデルで入出力(I/O)による状態変化を取り扱えるため、IOモナドと呼ばれます。
IOモナドと関数をbindし、さらにそれを関数にbindすると別のIOモナドが出来上がる。
普通はプログラマにその詳細は見えないようになっている。オブジェクト指向でのカプセル化に近い。
結果をアンボックス化タプルに入れることで、順番に評価されることが保証される。
詳細
リストモナド
bind(>>=)とreturnで操作できる対象をモナドと呼ぶ(ただしモナド則に則る)
- 中に値が入っていること
- bindでモナドと関数をつなぎ、新しいモナドを作れる
- returnでモナドを作れる
これが共通点。
リストモナドはIOモナドと違って、中に直接値が入っている。
printはIOモナドを返すため、リストモナドとはbindできない。
まとめ
- bind(>>=)とreturnで操作できる対象をモナドと呼びます(ただしモナド則に則る)
- リストはモナドの一種です
- IOモナドは中に値を生成する関数がありますが、リストは値が直接入っています(すべてのモナドが一緒なわけではない)
- 副作用を扱うのはIOモナド特有で、モナド共通の特徴ではありません(ここでいう副作用は状態の変化のことで、外部に影響を与えるもののこと)
- doブロックは共通の見た目で記述できますが、モナドによって動きが異なります(1つのdoで使えるモナドは1つだけ)
- IOモナドと違ってリストモナドは複数の値が入る
- bindはモナドによって動きが違う
- リスト内包表記はdoの糖衣構文です
ココ重要。
モナド
・手続きの性質を持つ圏論(数学の一分野)由来の概念
・モナドによって純粋関数型でも手続きプログラミングが可能になる
・多態性を持つため組み込みの手続きより強力
— ちゅーん (@its_out_of_tune) 2015, 2月 23
モナド則
モナドを使うにあたってのルールだが、ルールを完璧に覚えていなければ使えないというわけでもない。
Prelude> :t 1 1 :: Num a => a Prelude> :t return 1 return 1 :: (Monad m, Num a) => m a
モナド則は順番は決まっていないが、全部を満たさなければならない。難しい…
圏論を真面目にやると1 2 3のどれかが満たされないやつが出てくるようになるかもしれない。
そういえば、絵は分かりやすいけど、なんで箱からモノを出し入れしないといけないのかがあんまり良くわからないかもしれない。
パーサー
- 作者: Graham Hutton,山本和彦
- 出版社/メーカー: オーム社
- 発売日: 2009/11/11
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 503回
- この商品を含むブログ (117件) を見る
- 関数型パーサー
- yaccだとしんどい
- 逆アセンブラの実装は楽だけど、アセンブラの実装はしんどい
- ,やスペースをひとつひとつ解析しないといけないのが理由
パーサーは状態を持つのでモナドを使っている。
https://www.shido.info/hs/haskell2.html
あと、サポートページからソースコードをダウンロードできるけど、ダウンロードされるファイルは.lhsなので、気をつけておこう。