IO (Maybe String)を触ってみる


LT駆動の発表内容の下書きです。
下書きといっても十分にかいているつもりである。（読みやすくとはいっていない)

* [そのときのスライド](https://speakerdeck.com/eiel/io-maybe-a)

`IO (Maybe String)`はモナドが入れ子になっている。IOなんかついてるととても扱いづらい。実際に体験してみたりしてみる。


`IO String`は **実行**するたびに文字列が返ってくる。返ってくる文字列の内容は実行するたびに変わるかもしれない。

`Maybe String`は **評価**すると文字列もしくは空っぽかもしれない値が返ってくる。評価するたびに結果は変わらない。

`IO (Maybe String)`は、実行するたびに文字列もしくは空っぽの値が返ってくる。値は実行するたびに変わるかもしれない。

`IO Maybe String`ではない。
`(IO Maybe) String`と解釈されるため。これは型として不正。
IOは一つの型しか受け取らないが２つの型を受け取ろうとしているからである。

カインドを確認してみる。

```
ghci> :kind IO
IO :: * -> *
ghci> :kind Maybe
Maybe :: * -> *
ghci> :kind String
String :: *
ghci> :kind Maybe String
Maybe String :: *
```

`Maybe String`が`*`なのでIOに渡せるわけです。
というわけで、`Maybe (IO String)`という型もつくれる。

実際の話をしよう。
環境変数を取得する`lookupEnv`をつかってみます。

```
ghci> :type System.Environment.lookupEnv
System.Environment.lookupEnv :: String -> IO (Maybe String)
```

環境変数`Hoge`を取得してみる

```
$ stack ghci
ghci> :type System.Environment.lookupEnv "Hoge"
System.Environment.lookupEnv "Hoge" :: IO (Maybe String)
ghci> System.Environment.lookupEnv "Hoge"
Nothing
```

`lookupEnv`に引数を与えているので、型は`IO (Maybe String)
`になっています。
結果が`Nothing`なのは`Hoge`が未定義のためです。

```
$ Hoge="abc" stack ghci
ghci> System.Environment.lookupEnv "Hoge"
Just "abc"
```

```
$ Hoge="" stack ghci
ghci> System.Environment.lookupEnv "Hoge"
Just ""
```

さて、実際に、`IO (Maybe String)`な値をつかっていくところをみていきます。

環境変数Hogeを愚直にdoをつかってかいてみます。

```haskell
import System.Environment
import Data.Maybe

sample = do                      -- IOのためのdo
  maybeHoge <- lookup "Hoge"     -- maybeHoge は IOがとれて Maybe String　という型になっている
  return $ do                    -- Maybe のためのdo IOのdoの中にかけるように returnをつける
　　  hoge <- maybeHoge           -- hogeはMaybeがはずれて String
　　  return . putStrLn $ hoge    -- hogeの内容を出力。コンパイルが通るように returnをつけてあげる
```

細かい事を気にせずかいてみました。doが二重構造になっています。

sample関数の結果はつかってみます。

```
$ stack ghci
ghci> sample >>= fromMaybe (putStrLn "not found Hoge")
not found Hoge
```

```
$ Hoge=aaa stack ghci
ghci> sample >>= fromMaybe (putStrLn "not found Hoge")
aaa
```

`sample`だけでは動作しません。
Hogeがなかったときの処理を追加して、Maybeを外してやることで動作します。

`sample`の型は`IO (Maybe (IO ()))`になっています。`IO (Maybe ()`になってて欲しいところがこうなってるために

sampleをつくるときにHogeがなかったときの処理を追加しておくことでこの問題を回避してみます。

```
sample2 = do
  maybeHoge <- lookupEnv "Hoge"
  let hoge = fromMaybe "not found" maybeHoge    -- この時点でMaybeを外しておく。Nothingなら "not found"になる
  putStrLn hoge
```

```
$ stack ghci
ghci> sample2
not found
```

```
$ Hoge="aaa" stack ghci
ghci> sample2
aaa
```

`sample2`の型は`IO ()`になっています。
Hogeが未定義だった時の処理は埋め込まれてしまい、後で制御ができなくなってしました。

最初の例では`IO`と`Maybe`の２つのコンテナを活用していて、相互に利用していると絡まってしまいました。
ふたつめの例複雑さを抑えるために`Maybe`を排除していましました。

３つめの方法では `IO`と`Maybe`をくっつけて、 `MaybeT IO`を作ってみます。

```
import System.Environment
import Data.Maybe
import Control.Monad.Trans.Maybe

sample3 = do
  hoge <- MaybeT $ lookupEnv "Hoge" -- IO (Maybe String)を MaybeT IO Stringに変換した。箱は一つなので <- で値をひっぱてくると Stringになっている
  lift $ putStrLn hoge  -- IO () なので MaybeT IO () に変換
```

```haskell
$ stack ghci
ghci> runMaybeT sample3   -- IO (Maybe String)になるので実行できて、 Maybe Stringになる
Nothing
ghci> runMaybeT sample3 >>= maybe (putStrLn "not found") (const return ())  -- 後から処理の追加もできる
 "notFound"
```

```
$ Hoge=aaa stack ghci
ghci> runMaybeT sample3
aaa
ghci> runMaybeT sample3 >>= maybe (putStrLn "not found") (const return ())
aaa
```

sample3を使うには`runMaybeT`を使う必要があります。
これは `MaybeT IO a`を`IO (Maybe a)`に戻すことができます。
IOにすることができたので実行できます。
sample3の特徴は、値がなかったときの処理を後から追加できることです。

この方法の良い所は、`MaybeT IO a`に変換するのは難しくないことです。

MaybeTを使わずに、sample3と同じような動きになるように、つくるとこんな感じになりました。

```
sample1_5 = do
  maybeHoge <- lookupEnv "Hoge"
  case maybeHoge of
    Just hoge -> putStrLn hoge >> return (Just ())
    Nothing -> return Nothing
```

Maybeの中身を一回紐解いて、IOの処理をして、再度手動でMaybe化してあげないといけなくなってます。
この作業はボイラープレートになるようなのでMaybeTを使うほうがシンプルに記述できるようです。

MaybeTへの変換は、紐解く必要がなく、何かの関数をくっつけるだけです。
よくありそうな変換を表にしてみます。

| xの型 | 変換方法 | 型 |
|-------|----------|----|
| String  | return x | MaybeT IO String |
| Maybe String | MaybeT . return $ x | MaybeT IO String |
| IO String | lift x | MaybeT IO String |
| String -> String | fmap x | MaybeT IO String -> MaybeT IO String |
| String -> IO () | lift . x  | String -> MaybeT IO ()
| String -> Maybe a | MaybeT . return . x | String -> MaybeT IO a

![Kobito.ZEMiWt.png](https://qiita-image-store.s3.amazonaws.com/0/5840/3439e736-22bf-be2e-db1b-30c03cc01c75.png "Kobito.ZEMiWt.png")

# リンク

[IO (Maybe a)というタイトルでモナド変換子について学んだことを話した - #LT駆動 - そんなこと覚えてない](http://blog.eiel.info/blog/2016/07/03/ltdd-27-io-maybe/)