[Haskell] モナドの計算を中断する

単純に考えると、Haskell は関数型プログラミング言語のため、純粋な関数内では他のプログラミング言語における「関数の途中でエラー終了」はできません。

ただし、モナドを返す場合はそれに似た書き方が可能です。

(※実際には純粋な関数内でも例外を投げて計算を中断可能ですが、`IO` モナド外で投げられた例外は捕まえられないため、純粋な関数内で例外を投げることは避けるべきです。)

参考「[throw - Control.Exception](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Exception.html#v:throw)」
参考「[Catching Exceptions - Control.Exception](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Exception.html#g:3)」

# 0. まとめ

主に `empty` の値を返すことでモナドの計算を中断することができます。

`IO` モナドの場合は例外によって計算を中断できますが、やり方に注意が必要です (※後述) 。

それら以外の値でも中断できることがあります。

型クラス別:

- `Alternative`
    - `empty`
- `MonadPlus`
    - `mzero`
- `MonadFail`
    - `fail s`
- その他
    - `Either` 型の `Left a` 等

型別の例:

- `IO`
    - `empty`
    - `mzero`
    - `fail s`
    - `throwIO e`
    - `evaluate a`
    - `error s`, `errorWithoutStackTrace s`
- `Maybe`
    - `empty`
    - `mzero`
    - `fail s`
    - `Nothing`
- `[]`
    - `empty`
    - `mzero`
    - `fail s`
    - `[]`
- `Either`
    - `Left a`

※ `mzero` は歴史的な理由により残されていますが、`mzero = empty` です。
※ `Monoid` クラスの `mempty` は意味が異なります。`Alternative` クラスの `empty` と同じ値の場合もありますが、必ずそうなっているわけではないため、モナドの計算を中断する目的では使用しません。
※ `fail` 関数は `do` 記法で自動的に利用される関数であり、`fail s` を直接書くべきではありません。
※ `IO` 型では意味的には `fail = throwIO . userError` です。
※戻り値が `IO` 型でない関数から投げられた例外は `IO` モナド内であっても遅延評価され、意図したタイミングで計算を中断できないことがあります。`evaluate` 関数によって確実に中断することができます。
※ `error` 関数および `errorWithoutStackTrace` 関数は `ErrorCall` 例外を投げます。戻り値が `IO` 型の場合はそのまま使用でき、戻り値が `IO` 型でない場合は `evaluate` 関数と組み合わせる必要があります。

以下の関係が成り立ちます。

```haskell
empty >>= f = empty
empty *>  x = empty

fail s >>= f = fail s
fail s *>  x = fail s

Left a >>= f = Left a
Left a *>  x = Left a
```

`evaluate` 関数は `pure` 関数と同じような使い方をします。

ここでは説明のため型クラス制約を略しますが、型変数 `f` の型は `Applicative` クラスのインスタンスです。

```haskell
pure     :: a -> f  a -- Applicative
evaluate :: a -> IO a -- IO
```

参考「[empty - Control.Applicative](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Applicative.html#v:empty)」
参考「[mzero - Control.Monad](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#v:mzero)」
参考「[fail - Control.Monad](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#v:fail)」

参考「[3.14 Do Expressions - 3 Expressions](https://www.haskell.org/onlinereport/haskell2010/haskellch3.html#x8-470003.14)」
参考「[The fail method - Haskell/do notation - Wikibooks, open books for an open world](https://en.wikibooks.org/wiki/Haskell/do_notation#The_fail_method)」

参考「[IO - System.IO](https://hackage.haskell.org/package/base-4.19.0.0/docs/System-IO.html#t:IO)」
参考「[throwIO - Control.Exception](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Exception.html#v:throwIO)」
参考「[userError - System.IO.Error](https://hackage.haskell.org/package/base-4.19.0.0/docs/System-IO-Error.html#v:userError)」

参考「[catch - Control.Exception](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Exception.html#v:catch)」
参考「[evaluate - Control.Exception](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Exception.html#v:evaluate)」

参考「[error - Prelude](https://hackage.haskell.org/package/base-4.19.0.0/docs/Prelude.html#v:error)」
参考「[errorWithoutStackTrace - Prelude](https://hackage.haskell.org/package/base-4.19.0.0/docs/Prelude.html#v:errorWithoutStackTrace)」
参考「[ErrorCall - Control.Exception](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Exception.html#t:ErrorCall)」

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

# 1. 使い方

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

main :: IO ()
main = do

    -- IO
    do
        throwIO $ userError "Error" :: IO ()
        putStrLn "Foo"              :: IO ()
      `catch` \e -> do
        hPrint stderr (e :: IOError)

    do
        errorWithoutStackTrace "Error" :: IO ()
        putStrLn "Foo"                 :: IO ()
      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)

    do
        evaluate (errorWithoutStackTrace "Error" :: ()) :: IO ()
        putStrLn "Foo"                                  :: IO ()
      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)

    -- Maybe
    print $ do
        Nothing :: Maybe ()
        Just 23 :: Maybe Int

    -- []
    print $ do
        []       :: [()]
        [23, 42] :: [Int]

    -- Either
    print $ do
        Left "Error" :: Either String ()
        Right 23     :: Either String Int
```

```text:実行結果
user error (Error)
Error
Error
Nothing
[]
Left "Error"
```

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

main :: IO ()
main = do

    -- IO
    do
        x :: Int <- throwIO $ userError "Error" :: IO Int
        putStrLn "Foo" :: IO ()
        print x        :: IO ()
      `catch` \e -> do
        hPrint stderr (e :: IOError)

    do
        x :: Int <- evaluate (23 `div` 0 :: Int) :: IO Int
        putStrLn "Foo" :: IO ()
        print x        :: IO ()
      `catch` \e -> do
        hPrint stderr (e :: ArithException)

    do
        x :: Int <- errorWithoutStackTrace "Error" :: IO Int
        putStrLn "Foo" :: IO ()
        print x        :: IO ()
      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)

    do
        x :: Int <- evaluate (errorWithoutStackTrace "Error" :: Int) :: IO Int
        putStrLn "Foo" :: IO ()
        print x        :: IO ()
      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)

    -- Maybe
    print $ do
        x :: Int <- Nothing :: Maybe Int
        Just x :: Maybe Int

    -- []
    print $ do
        x :: Int <- [] :: [Int]
        [x, 42] :: [Int]

    -- Either
    print $ do
        x :: Int <- Left "Error" :: Either String Int
        Right x :: Either String Int
```

```text:実行結果
user error (Error)
divide by zero
Error
Error
Nothing
[]
Left "Error"
```

# 2. 仕組み

## 2.1. `Alternative`: `empty`

インスタンス `Alternative f` に関して、`empty :: f a` の値は `a` 型の値を持たないため、`a -> b` 型や `a -> f b` 型の関数を適用すると `empty :: f b` の値になります。

`Alternative` クラスのインスタンスに関して、以下の関係が成り立ちます。

```haskell
f <$> empty = empty
f <*> empty = empty
empty <*> x = empty

empty $> x = empty
empty *> x = empty
```

`Alternative` クラスと `Monad` クラスの両方のインスタンスの場合、以下の関係が成り立ちます。

```haskell
empty >>= f = empty

do
    x <- empty
    f x
= empty

do
    empty
    x
= empty
```

## 2.2. `MonadPlus`: `mzero`

`MonadPlus` クラスは `Alternative` クラスと `Monad` クラスを継承するクラスです。

歴史的な理由により残されていますが、`Alternative` クラスと意味は同じです。

参考「[MonadPlus - Control.Monad](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#t:MonadPlus)」

## 2.3. `MonadFail`: `fail s`

`MonadFail` クラスの `fail` 関数は、`do` 記法の `<-` で左側のパターンが一致しないとき、自動的に利用されます。

```haskell
z = do
    C y <- x
    f y
```

```haskell:意味
z = x >>= g
  where
    g (C y) = f y
    g _     = fail "Pattern match failure"
```

`fail s` は以下の条件を満たすよう定義されます。

```haskell
fail s >>= f = fail s
```

`MonadFail` クラスと `Alternative` クラスの両方のインスタンスの場合、以下のように定義されることがあります。

```haskell
fail _ = empty
```

`MonadFail` クラスは例外を扱うためのものではありませんが、`IO` 型を含む `MonadIO` クラスのインスタンスでは `fail s` は例外を投げます。

`IO` 型での `fail` 関数の意味は以下のようになっています。

```haskell:意味
fail :: String -> IO a
fail = throwIO . userError
```

参考「[MonadFail - Control.Monad](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#t:MonadFail)」

## 2.4. 戻り値が `IO` 型でない関数から投げられた例外を `IO` モナド内で捕まえられるようにする

Haskell では、`IO` モナド内で投げられた例外は捕まえることができますが、`IO` モナド外で投げられた例外は捕まえることができません。

戻り値が `IO` 型でない関数から投げられた例外は `IO` モナド内であっても遅延評価され、意図したタイミングで計算を中断できないことがあります。

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

main :: IO ()
main = do
    let x = 23 `div` 0 :: Int
    putStrLn "Foo" :: IO ()
    print x        :: IO ()
  `catch` \e -> do
    hPrint stderr (e :: ArithException)
```

```text:実行結果
Foo
divide by zero
```

`evaluate` 関数で `IO` モナドでない値を `IO` モナドにすると、確実に中断することができます。

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

main :: IO ()
main = do
    x :: Int <- evaluate (23 `div` 0 :: Int) :: IO Int
    putStrLn "Foo" :: IO ()
    print x        :: IO ()
  `catch` \e -> do
    hPrint stderr (e :: ArithException)
```

```text:実行結果
divide by zero
```

## 2.5. `error` 関数および `errorWithoutStackTrace` 関数

`error` 関数および `errorWithoutStackTrace` 関数は `ErrorCall` 例外を投げます。

`IO` モナド内で呼ぶ場合、`error` 関数および `errorWithoutStackTrace` 関数の戻り値が `IO` 型かによって挙動が異なるため注意が必要です。

### 2.5.1. 戻り値が `IO` 型の場合

`error` 関数および `errorWithoutStackTrace` 関数の戻り値が `IO` 型の場合、そのまま使用して意図したタイミングで計算を中断できます。

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

main :: IO ()
main = do
    errorWithoutStackTrace "Error" :: IO ()
    putStrLn "Foo" :: IO ()
  `catch` \e -> do
    hPrint stderr (e :: ErrorCall)
```

```text:実行結果
Error
```

### 2.5.2. 戻り値が `IO` 型以外の場合

`error` 関数および `errorWithoutStackTrace` 関数の戻り値が `IO` 型以外の場合、遅延評価によって意図したタイミングで計算を中断できなかったり、そもそも評価されずに中断されないことがあります。

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

main :: IO ()
main = do
    let x = errorWithoutStackTrace "Error" :: Int
    putStrLn "Foo" :: IO ()
    print x        :: IO ()
  `catch` \e -> do
    hPrint stderr (e :: ErrorCall)
```

```text:実行結果
Foo
Error
```

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

main :: IO ()
main = do
    pure (errorWithoutStackTrace "Error" :: ()) :: IO ()
    putStrLn "Foo" :: IO ()
  `catch` \e -> do
    hPrint stderr (e :: ErrorCall)
```

```text:実行結果
Foo
```

`evaluate` 関数を用いることで、確実に中断することができます。

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

main :: IO ()
main = do
    x :: Int <- evaluate (errorWithoutStackTrace "Error" :: Int) :: IO Int
    putStrLn "Foo" :: IO ()
    print x        :: IO ()
  `catch` \e -> do
    hPrint stderr (e :: ErrorCall)
```

```text:実行結果
Error
```

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

main :: IO ()
main = do
    evaluate (errorWithoutStackTrace "Error" :: ()) :: IO ()
    putStrLn "Foo" :: IO ()
  `catch` \e -> do
    hPrint stderr (e :: ErrorCall)
```

```text:実行結果
Error
```