Skip to content

Commit 1c6bad4

Browse files
authored
Merge pull request #190 from mizunashi-mana/fix-iomonad-article
記事の修正: IO モナドと 副作用
2 parents a324b64 + c632a7b commit 1c6bad4

File tree

1 file changed

+10
-12
lines changed

1 file changed

+10
-12
lines changed

preprocessed-site/posts/2020/io-monad-and-sideeffect.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Haskell は純粋関数型プログラミング言語 (purely functional program
2222
>
2323
> Haskell の全ての関数は、数学の意味での関数 (つまり「純粋」) です。
2424
>
25-
> -- https://www.haskell.org/
25+
> -- [haskell.org](https://www.haskell.org/) Features: Purely functional より
2626
2727
ふむ、どうやら全ての関数が、数学的な意味での関数であれば、そのプログラミング言語は純粋と言えるようだ。ところで、数学的な意味での関数とはなんだろうか? 関数が純粋とはどういうことを指すんだろうか? これは噛み砕くと、
2828

@@ -100,9 +100,7 @@ putStrLn :: String -> IO ()
100100
型だ抽象的すぎてあまりピンとこないかもしれないもしその動作が結果を返す以外に何もしないならそれは純粋な操作であるから次のように書ける:
101101

102102
```haskell
103-
data PureAction a = PureAction
104-
{ runPureAction :: () -> a
105-
}
103+
data PureAction a = PureAction (() -> a)
106104
```
107105

108106
つまり引数が何もない純粋関数だ例えば整数を2つ受け取ってその和を計算する動作を返す関数は次のように書けるだろう:
@@ -137,9 +135,9 @@ addAction x y = PureAction (\_ -> x + y)
137135
2. 結果を捨て
138136
3. `()` を返す
139137

140-
というプログラムだ。このプログラムを評価しても、結果の `()` だけしか目にしないはずで、何回実行しても同じ結果が得られるはずだ。そう説明すると、ちょっと Haskell をかじった人は
138+
というプログラムだ。このプログラムを評価しても、`()` だけしか目にしないはずで、何回実行しても同じ結果が得られるはずだ。つまり、`putStrLn` は余計なことを何もしていないと言えるだろう。そう説明すると、ちょっと Haskell をかじった人は
141139

142-
> この説明は間違っている。この式は `putStrLn "str"` を全く評価していないので、実際に `putStrLn "str"` が余計なことを何もしていないかは分からない
140+
> この説明は間違っている。この式は `putStrLn "str"` を全く評価していないので、実際に `putStrLn "str"` が余計なことを何もしていないかは分からない
143141
144142
と言うだろう。その通りだ。この説明は間違っている。それを確認してみよう:
145143

@@ -148,7 +146,7 @@ addAction x y = PureAction (\_ -> x + y)
148146
()
149147
```
150148

151-
もし、さっきの `putStrLn "str"` がちゃんと計算されていたなら、今回は `something happened!` というエラーが見れるはずだ。ところが、全く何の問題もなく式の実行は終わり、`()` が出力されてしまった。では、ちゃんと修正してみよう。修正は、`seq` という魔法の関数を使うことで可能だ:
149+
もし、さっきの `putStrLn "str"` がちゃんと計算されていたなら、今回は `something happened!` というエラーが見れるはずだ。ところが、全く何の問題もなく式の実行は終わり、`()` が出力されてしまった。Haskell は遅延評価により、最終結果に本当に必要な部分しか計算してくれないので、`putStrLn "str"` の部分は計算されず無視されてしまっていただけのようだ。では、ちゃんと修正してみよう。修正は、`seq` という魔法の関数を使うことで可能だ`seq :: a -> b -> b` は一番最初に渡された引数を (必要かどうかに関わらず、強制的に) 計算し、その後2番目の引数を返す関数だ。この関数を使うと、次のように修正が可能だ:
152150

153151
```haskell
154152
>>> putStrLn "str" `seq` ()
@@ -157,7 +155,7 @@ addAction x y = PureAction (\_ -> x + y)
157155
*** Exception: something happened!
158156
```
159157

160-
今度は大丈夫だろう。`putStrLn "str"` の部分をエラーに変えると、ちゃんとエラーが出力されている。そう、`putStrLn "str"` が実行されて実際に行われるのは、その定義通り
158+
今度は大丈夫だろう。`putStrLn "str"` の部分をエラーに変えると、ちゃんとエラーが出力されている。`putStrLn "str"` は計算されているようだ。そう、`putStrLn "str"` が実行されて実際に行われるのは、その定義通り
161159

162160
* 「ターミナルに `"str"` を出力する動作」を返す
163161

@@ -410,7 +408,7 @@ Haskell の IO モナドとは、動作そのものを値に持つ型だった
410408

411409
ところで、もしかしたら、読者の中には、
412410

413-
> Haskell の IO モナドは、現実世界を状態にする State モナドだ
411+
> Haskell の IO モナドは、現実世界を状態にする State モナドだ
414412
415413
という主張を、見たことがある人がいるかもしれない。最後におまけとしてこの話に触れておこうと思う。気になる人は、この後も呼んでみると、`IO` モナドの理解の助けになるかもしれない (または、むしろ混乱するかもしれない。もし、混乱したなら、とりあえずこの話は忘れることをお勧めする。ここに書いてある話を理解しなくても、`IO` モナドの利用に関して全く支障はない。そういう話もあるぐらいの事柄だ。なので、安心してまずは Haskell プログラミングを楽しんでほしい。いつか楽しみ飽きたら戻ってきてもいいかもしれない)。
416414

@@ -493,14 +491,14 @@ IO $ \r0# ->
493491
False
494492
```
495493

496-
`b1``b2` は両方とも `readMutVar# var# r1#` から得た値になる。ところが、これらを比較してみると `False` になる [^notice-undefined-behavior] 。なお、この式は、`IO` 型で定義しているが、実際には
494+
`b1``b2` は両方とも `readMutVar# var# r1#` から得た値になる。ところが、これらを比較してみると `False` になる [^notice-undefined-behavior]もし、`readMutVar#` が純粋なら、`b1``b2` の結果は同じになるため、上の評価結果は `True` になるはずだ。しかし、残念ながら `readMutVar#` は純粋ではないので、`b1``b2` は異なる値になってしまう。なお、この式は、`IO` 型で定義しているが、実際には
497495

498496
* 2 回目の `readMutVar#` の呼び出しで `r1#` を 2 回使用しているし、
499497
* 返ってきた `State# RealWorld` の値を捨てている
500498

501499
[^notice-undefined-behavior]: 実際には、最適化次第で結果が変わることもある。
502500

503-
ので契約違反であることに注意だ。
501+
ので契約違反であることに注意だ。GHCi 上で、うまく評価結果を確認するために、`IO` を使っている。
504502

505503
さて、純粋性を守れないなら、GHC は一体全体何のためにこのような定義をしているんだろう? 関数が純粋でなくてもいいなら、単に
506504

@@ -557,7 +555,7 @@ IO f >>= g = IO $ \r0# ->
557555
特に、`(>>=)` の定義が重要になる。`(>>=)` が返してくる `IO` の中身は、
558556

559557
1. 受け取った `State# RealWorld` をまず最初の `IO` 動作に渡す
560-
2. その結果出てきた結果を `g` に渡して、次の `IO` 動作を生成する
558+
2. その結果を `g` に渡して、次の `IO` 動作を生成する
561559
3. 生成した `IO` 動作に、最初の `IO` 動作が返してきた `State# RealWorld` を渡す
562560

563561
ということを行っている。これにより、

0 commit comments

Comments
 (0)