IOモナドのよもやま話 (Haskell)

Haskellでプログラムを書くときは、必ずIOモナドを使いますよね。なんてったって、main関数がIOモナドですから！！

```haskell
main :: IO ()
main = do
    print "Hello"
    print "World"

-- > Hello
-- > World
```

でも、なんでIOモナドなんてややこしい道具を使うのでしょうか？

これには二つの理由があります。

## IOモナドは副作用のカプセル化

一つ目の理由は、IOモナド以外の関数について[参照透過性](https://ja.wikipedia.org/wiki/%E5%8F%82%E7%85%A7%E9%80%8F%E9%81%8E%E6%80%A7)を維持するためです。

ユーザーからの入力を受け付ける関数に`getLine`がありますよね。もし、この関数がIOモナドを使わずに`getLine :: String`と定義されていたら、何が起こるか考えてみましょう。

```haskell
addTitle :: String -> String
addTitle tt = getLine ++ tt
```

関数getLineを使って、関数addTitleを定義してみました。

あるユーザーの入力が"斎藤　浩司"[^2]だったときの関数addTitleの動きを確認しましょう。

|引数|返り値|
|-|-|
|"さん"|"斎藤　浩司さん"|
|"さま"|"斎藤　浩司さま"|
|"社長"|"斎藤　浩司社長"|

次に、別のユーザーの入力が"木村　栞"[^2]だったときの関数addTitleの動きを確認しましょう。

|引数|返り値|
|-|-|
|"さん"|"木村　栞さん"|
|"さま"|"木村　栞さま"|
|"社長"|"木村　栞社長"|

同じ引数でも返り値が異なっていますね。

これは、参照透過性のルールに反しています。しかし、この関数が参照透過性のルールに反していることは関数の型を見てもわからないので、処理内容を逐一確認する必要があります。どうにかして、関数の型を見るだけで参照透過性の有無が分かるようにしたいですよね。そこで、参照透過性の無い処理（= 副作用を伴う処理）をIOモナドに包んでしまうことにしたのです。

## IOモナドは同期処理の仕組み

もう一つ理由は、(1)すべての副作用が、(2)意図した順番で、実行されることを保証するためです。

### (1)すべての副作用が実行されることの保証について

普通にprint関数を`print :: String -> ()`で定義して、処理を順番に行い最後の処理の結果を返す演算子`;`を定義して[^3]、

```haskell
main :: ()
main = print "Hello" ; print "World"
```

みたいにしたらダメなんでしょうか？

これではダメなんです。こうすると"Hello"が表示されなくなるんです。

これは、Haskellが遅延評価でプログラムを実行するからです。言い換えると、必要になるまではプログラムは実行されないということです。演算子`;`は最後の処理（= 式）の結果を返す演算子でした。そのため、main関数の結果を計算するうえでは、`print "World"`の部分の実行結果のみが必要となり実行されます。一方、`print "Hello"`の部分の実行結果は必要とされないので、まったく実行されずにプログラムが終了します。その結果、"Hello"が表示されません。

### (2) 副作用が意図した順番で実行されることの保証について

渡された文字列を画面に表示した後、画面に表示した文字数を返すような関数printNを考えます。

関数printNが`printN :: String -> Integer`と定義されているとき、

```haskell
main :: ()
main = let x = print "Hello", y = print "World" in y + x
```

このプログラムを実行すると、画面に"World Hello"と表示されます。。。あれ？？

"Hello"と"World"が逆さまになってしまいました。これは、`y + x`の部分を評価するときに、変数`y`つまり`print "World"`のほうが先に実行されてしまうために起こります。

```haskell
main = do
    x <- unsafeInterleaveIO (print "eval")
    print "end"
    print x
```

## UnsafeIO

こうして、IOモナドによって副作用がうまく扱えるようになりました。めでたしめでたし。。。

とはならないんです。

IOモナドは副作用の扱いを楽にしてくれる代わりに、多くの制約を伴います。そのため、副作用の取り扱いは慎重に行うから、IOモナドを経由せずに副作用を使いたいという要望がでてきます。そういう人のために、IOモナドの制約を緩める関数群が[System.IO.Unsafeパッケージ](https://hackage.haskell.org/package/base-4.16.0.0/docs/System-IO-Unsafe.html)に用意されています。

ここでは、System.IO.Unsafeパッケージの関数のうち、重要な二つの関数を紹介します。

### unsafePerformIO

```haskell:型
unsafePerformIO :: IO a -> a
```

IOモナドを使う理由の一つ目として、[IOモナドは副作用のカプセル化](#ioモナドは副作用のカプセル化)であると説明しました。この関数は、そのカプセル化の制約を緩めます。つまり、IOモナドから副作用の結果を取り出します。

この関数がunsafe（= 安全でない）とされているのは、IOモナドから副作用の結果を取り出すことによって、参照透過性が損なわれる可能性があるからです。そのため、この関数を使う際は、参照透過性に十分注意する必要があります。

```haskell
addTitle :: String -> String
addTitle tt = (unsafePerformIO getLine) ++ tt

main = print $ addTitle "さん"

-- < 斎藤　浩司
-- > 斎藤　浩司さん
-- < 木村　栞
-- > 木村　栞さん
```

また、[IOモナドは同期処理の仕組み](#ioモナドは同期処理の仕組み)で説明した、副作用の実行保証と実行順序保証もなくなります。

```haskell
printN = \s -> do
    print s
    return (length s)

let x = unsafePerformIO (print "Hello"); y = unsafePerformIO (print "World") in y + x

-- > World
-- > Hello
```

さらに、この関数は型安全ではありません。例えば、次のプログラムは実行時エラーとなります。

```haskell
test :: IORef [a]
test = unsafePerformIO $ newIORef []

main = do
    writeIORef test [42]
    bang <- readIORef test
    print (bang :: [Char])
```
（[System.IO.Unsafe - Hackage](https://hackage.haskell.org/package/base-4.16.0.0/docs/System-IO-Unsafe.html)より引用）

詳しくは、[System.IO.Unsafe - Hackage](https://hackage.haskell.org/package/base-4.16.0.0/docs/System-IO-Unsafe.html)を参照してください。

### unsafeInterleaveIO

```haskell
unsafeInterleaveIO :: IO a -> IO a
```

この関数は、[IOモナドは同期処理の仕組み](#ioモナドは同期処理の仕組み)で説明した、副作用の実行保証と実行順序保証の制約を緩めます。

```haskell:副作用の実行が保証されない
printN = \s -> do
    print s
    return (length s)

main = do
    x <- unsafeInterleaveIO (printN "Hello")
    y <- unsafeInterleaveIO (printN "World")
    print x

-- > Hello
-- > 5
```

```haskell:副作用の実行順序が保証されない
printN = \s -> do
    print s
    return (length s)

main = do
    x <- unsafeInterleaveIO (printN "Hello")
    y <- unsafeInterleaveIO (printN "World")
    print (y + x)

-- > World
-- > Hello
-- > 10
```

## 練習問題 (難易度★)

次のプログラムを実行すると、画面に何と表示されるでしょうか。

```haskell
printN = \s -> do
    print s
    return (length s)

hello = unsafePerformIO (printN "Hello")

main = do
    let x = hello
        y = hello
    print (x + y)
```

<details><summary>答え</summary><div>

```shell
> Hello
> 10
```

元ネタは[禁断の機能「unsafePerformIO」の深淵](https://xtech.nikkei.com/it/article/COLUMN/20090512/329783/)なので、詳しくはそちらの記事を参照してください。

</div></details>

## 参考資料

- [禁断の機能「unsafePerformIO」の深淵](https://xtech.nikkei.com/it/article/COLUMN/20090512/329783/)
- [System.IO.Unsafe - Hackage](https://hackage.haskell.org/package/base-4.16.0.0/docs/System-IO-Unsafe.html)

[^2]: 実在の人物との関係はありません。。。
[^3]: セミコロン`;`は予約記号なので、実際には演算子として使えません。また、この演算子の働きは`flip const`ですね。