[Haskell] Either 型と IO 型の相互変換

`Either` 型から `IO` 型へ直接変換する関数は標準では提供されないため、`either` 関数を用いて変換します。

`IO` 型から `Either` 型へ変換するには `try` 関数または `tryJust` 関数を用います。

```haskell
-- 
either error                  pure :: Either String a -> IO a
either errorWithoutStackTrace pure :: Either String a -> IO a

either throwIO pure :: Exception e => Either e a -> IO a

-- 
try     :: Exception e =>                   IO a -> IO (Either e a)
tryJust :: Exception e => (e -> Maybe b) -> IO a -> IO (Either b a)
```

参考「[either - Data.Either](https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Either.html#v:either)」
参考「[try - Control.Exception](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Exception.html#v:try)」
参考「[tryJust - Control.Exception](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Exception.html#v:tryJust)」

モナドの計算を中断する方法については別記事にしています。

参考「[[Haskell] モナドの計算を中断する - Qiita](https://qiita.com/kerupani129/items/f6819466b49fe799b40c)」

# 1. `Either a b -> IO b`

値が `Left` であれば例外を投げ、値が `Right` であれば中身の値を `IO` 型に入れます。

(本記事では深く触れませんが、値が `Left` のときに例外を投げずデフォルト値に置き換えたい場合は `fromRight` 関数を用いて `Either a b -> b` の変換をします。)

参考「[fromRight - Data.Either](https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Either.html#v:fromRight)」

## 1.1. `Either String a -> IO a`

`either error pure` または `either errorWithoutStackTrace pure` とすることで `Either String a -> IO a` の変換を行えます。

```haskell
import Control.Exception (catch, ErrorCall)
import System.IO (hPrint, stderr)

main :: IO ()
main = do

    -- 
    do

        let x = Right 23 :: Either String Int

        y :: Int <- either error pure x :: IO Int

        print y

      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)

    do

        let x = Left "Error" :: Either String Int

        y :: Int <- either error pure x :: IO Int

        print y

      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)

    -- 
    do

        let x = Right 23 :: Either String Int

        y :: Int <- either errorWithoutStackTrace pure x :: IO Int

        print y

      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)

    do

        let x = Left "Error" :: Either String Int

        y :: Int <- either errorWithoutStackTrace pure x :: IO Int

        print y

      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)
```

## 1.2. `Exception e => Either e a -> IO a`

`either throwIO pure` とすることで `Exception e => Either e a -> IO a` の変換を行えます。

```haskell
import Control.Exception (catch, throwIO)
import System.IO (hPrint, stderr)

main :: IO ()
main = do

    do

        let x = Right 23 :: Either IOError Int

        y :: Int <- either throwIO pure x :: IO Int

        print y

      `catch` \e -> do
        hPrint stderr (e :: IOError)

    do

        let x = Left $ userError "Error" :: Either IOError Int

        y :: Int <- either throwIO pure x :: IO Int

        print y

      `catch` \e -> do
        hPrint stderr (e :: IOError)
```

## 1.3. その他

その他の `Either a b` 型の場合は `a` 型の値を `String` または `Exception e => e` 型に変換し、前述の方法を用いて `IO b` 型にします。

```haskell:例
import Control.Exception (catch, ErrorCall)
import System.IO (hPrint, stderr)

f :: Int -> IO Int
f x = do
    error $ "Error: " ++ show x

main :: IO ()
main = do

    do

        let x = Right 23 :: Either Int Int

        y :: Int <- either f pure x :: IO Int

        print y

      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)

    do

        let x = Left 42 :: Either Int Int

        y :: Int <- either f pure x :: IO Int

        print y

      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)
```

# 2. `IO a -> IO (Either b a)`

## 2.1. ` Exception e => IO a -> IO (Either e a)`

`try` 関数を用いると、例外を投げられていれば `Left` の値を返し、そうでなければ `Right` の値を返します。

```haskell
import Control.Exception (ErrorCall, throwIO, try)

main :: IO ()
main = do

    -- 
    do

        x :: Either ErrorCall Int <- try $ do
            pure 23 :: IO Int

        print x

    do

        x :: Either ErrorCall Int <- try $ do
            error "Error" :: IO Int

        print x

    do

        x :: Either ErrorCall Int <- try $ do
            errorWithoutStackTrace "Error" :: IO Int

        print x

    -- 
    do

        x :: Either IOError Int <- try $ do
            pure 23 :: IO Int

        print x

    do

        x :: Either IOError Int <- try $ do
            throwIO $ userError "Error" :: IO Int

        print x
```

## 2.2. `Exception e => (e -> Maybe b) -> IO a -> IO (Either b a)`

`tryJust` 関数も `try` 関数と同様ですが、例外を投げられていれば他の型に変換してから `Left` の値を返します。

例外が投げられていなければ `try` 関数と同様にそのまま `Right` の値を返します。

(`tryJust` 関数は他の使い方がありますが、本記事では不説明。)

```haskell:例
import Control.Exception (ErrorCall, throwIO, tryJust)

main :: IO ()
main = do

    -- 
    do

        x :: Either String Int <- tryJust
            (\e -> pure $ show (e :: ErrorCall))
            $ do
                pure 23 :: IO Int

        print x

    do

        x :: Either String Int <- tryJust
            (\e -> pure $ show (e :: ErrorCall))
            $ do
                errorWithoutStackTrace "Error" :: IO Int

        print x

    -- 
    do

        x :: Either String Int <- tryJust
            (\e -> pure $ show (e :: IOError))
            $ do
                pure 23 :: IO Int

        print x

    do

        x :: Either String Int <- tryJust
            (\e -> pure $ show (e :: IOError))
            $ do
                throwIO $ userError "Error" :: IO Int

        print x
```