Haskellと副作用
#WIP
あとでMonad型クラスかIOモナドにまとめる
無理そうなら諦める
結論
Haskellに副作用はある
ただし、副作用があるものはIO型が付けられているので、それ以外の純粋なものたちとは明確に区別できる
また、モナドという構造があることで、その副作用が純粋な世界に流れ込んでくることはない
「純粋な世界」と「副作用を扱う世界」の明確な分離
IOのある世界が副作用を扱う世界
型を見るだけでIOするのかどうかを判断できる
IOから純粋関数は呼べるが、逆はできない
つまり、純粋な世界から、副作用の世界を呼ぶことはできない
純粋な世界では副作用がないため、遅延評価ができる
純粋な世界では遅延評価されるので記述した順序で実行されるとは限らない
副作用のある世界では記述順に実行される
入出力がある場合、記述順に実行されなければ、まだgetLineしてないのに、それがprintされちゃうとかがあるから、「入出力は遅延評価と相性が悪い」ってなるのか
「副作用を扱う」というのはIOモナド特有で、モナド共通の特徴ではない
「副作用のある処理」を「純粋関数」で表現する、という矛盾を解決するもの
関数は副作用を持ってはいけない
同じ引数で2回呼び出されたら必ず同じ結果にならないといけない
例えばgetLine(入力から文字列を読み込むやつ)は実行するたびに実行するたびに返り値が変わる
IOモナドは、「Stateモナドの一種」と見る
RealWorldという引数を毎回取る
なので、これは純粋関数である
RealWorldの値が変わっているだけ
要はnewtype IO a = IO (RealWorld -> (RealWorld, a))
参考
Haskell の IO モナドと参照透過性の秘密 - TIM Labs
細かく簡約過程が説明されていてわかりやすい
IO aは命令書であり、プログラムのレベルでは副作用はない
State系のモナドでは、do式の中にレシピを書いて最後にrunStateなどをして実行する
x -> m aでは、xの値が同じであれば、常に同じレシピを作成する
ここが純粋
IOでも同じで
x -> IO aがあって、xが同じであれば常に同じIO aを作る
IOの実行機はHaskell言語のランタイム
do式全体のactionを実行するタイミングで現実世界の状況をランタイムがレシピに伝える
と、言う意味でHaskellでは「IOモナドの中でのみ」副作用が発生し、それ以外の場所では副作用が発生しない
IOアクションを使う場所でも副作用は発生しない(?)
IOモナドの仲、ってどこ?
IOモナドはただの手続きであり、手続きを組み合わせるものであって、実際にIOを実行するわけではない
実際に処理をするのはHaskellの処理系、というかコンパイラが生成した実行ファイル
#??
getLineで何かを入力してputStrLnでその値が画面に出力されたとき、つまりはgetLine >>= putStrLnって書いたとき、「ターミナルで入力したもの」が同じなら、「画面に出力されるもの」は常におなじになると思うんだけど、これは純粋という意味ではない?
何を考えればわかりやすい?時間?ランダム数値?
これもHaskelでは純粋に扱えるのか?
Haskellの純粋「関数」と読んでいるのは、アクションは排除して呼んでるのか?
つまり関数は全て純粋だけど、副作用を扱うアクションもあるよ、的な
IOモナドの「副作用を閉じ込める」は度のスコープの話をしている?
一つのdoブロックの中での話?それとももっと小さなスコープの話?
「状態を持つこと」は副作用なの?
ならばStateモナドも副作用あり?
STモナドも副作用あり?
実行環境を考えたときに、起こる
わからないので言語化できない
この考え方、他の純粋でない言語とどう違うんだ
逆にdo式の中で遅延評価して、上から下ではない順に評価されることってあるの?
IOモナドとか関係なく、全てのモナドについて
IOはファーストクラス
入出力をするコードの生成と実行が別れている
生成するものの例は?
putStr "hoge"が実行されたとき
実行するものの例は?
main関数が実行されたとき
生成の方は純粋
code:hs
-- https://kazu-yamamoto.hatenablog.jp/entry/20091214/1260774669
as :: IO ()
as = putStr "World!", putStr "Hello, "
main :: IO ()
main = do
as !! 1
as !! 0
IOはファーストクラスなので
そうか、「IOを変数に格納できる」って結構やばいなmrsekut.icon
IO aのaは「IOを実行した結果」の型
IO Stringという部分に受けとる引数についての情報はなく、引数の情報はあくまでも普通の関数を表す記号 -> の左辺に現れるのです。ref
IOは引き数を取らない」わかるようなわからないような..mrsekut.icon
IOに対して何らかの処理を加えた結果は必ずIOになるref
なので、IOモナドは↑こういう文脈を持つ
Haskell has effects. The difference is that they're first-class values and not "to the side" ref
bindは順序付け
doも
bindは前の計算の結果を元に、次の計算をどの様に実行するかを決められるものだ
IO a -> (a -> IO b)という型から
IO bがaに依存して決まる値だということもわかる
doを使わなければ、「純粋関数の組み合わせ」であることがわかりやすくなる?
参考
Haskellには副作用がないのか? - あどけない話
どれも正しいのか、記事を読むごとにその人がどの立場なのかを判断しないといけない
「実行器までがHaskellだ」が最も一般的なのだろうか
Haskellと副作用 - あどけない話
igreque : Info -> HaskellのIOは他の言語でいうところの関数オブジェクトとよく似てるよ、という話
http://uehaj.hatenablog.com/entry/2013/11/06/125250
https://qiita.com/Mizunashi_Mana/items/e82214dfae2765c6839a
http://blog.livedoor.jp/igrep/archives/1591546.html
http://hiratara.hatenadiary.jp/entry/20121211/1355241685
https://haskell.jp/blog/posts/2020/io-monad-and-sideeffect.html
https://qiita.com/lambda_funtaro/items/2b157a4ca9fea02f5650
https://qtamaki.hatenablog.com/entry/20130624/1372086242