[Haskell] 条件分岐してモナドを返す

Haskell は関数型プログラミング言語のため、純粋な関数内では他のプログラミング言語における「`else` なしの `if`」はできません。

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

# 0. まとめ

何もしないモナドは `pure ()` で得られます。

条件分岐してモナドを返すための関数があります。

型クラス制約別:

- `Applicative`
    - `when`, `unless`
- `Alternative`
    - `guard`

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

```haskell
-- Applicative
when   :: Bool -> f () -> f ()
unless :: Bool -> f () -> f ()

-- Alternative
guard :: Bool -> f ()
```

# 1. 何もしない `Applicative` を返す `pure ()`

`pure ()` は何もしない `Applicative` を返し、以下の関係が成り立ちます。

```haskell
pure () *> x = x
```

よって、以下のようにモナドの計算中に入れても何もしません。

```haskell
main :: IO ()
main = do

    -- IO
    do
        pure ()        :: IO ()
        putStrLn "Foo" :: IO ()

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

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

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

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

    -- Either
    print $ do
        pure ()  :: Either String ()
        Right 23 :: Either String Int

    print $ do
        Right () :: Either String ()
        Right 23 :: Either String Int
```

```text:実行結果
Foo
Just 23
Just 23
[23,42]
[23,42]
Right 23
Right 23
```

よって、条件分岐でモナドを返すとき、何もしたくない場合は `pure ()` にします。

```haskell
f :: Int -> IO ()
f 0 = putStr "Bar " :: IO ()
f _ = pure ()       :: IO ()

g :: Int -> IO ()
g x
    | x == 0    = putStr "Bar " :: IO ()
    | otherwise = pure ()       :: IO ()

h :: Int -> IO ()
h x = do

    if x == 0
        then putStr "Bar " :: IO ()
        else pure ()       :: IO ()
    putStrLn "Foo" :: IO ()

    case x of
        0 -> putStr "Bar " :: IO ()
        _ -> pure ()       :: IO ()
    putStrLn "Foo" :: IO ()

    f x            :: IO ()
    putStrLn "Foo" :: IO ()

    g x            :: IO ()
    putStrLn "Foo" :: IO ()

main :: IO ()
main = do

    h 0
    h 23
```

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

# 2. `Applicative`: `when`, `unless`

`when` 関数を用いると「`else` なしの `if`」のような書き方ができます。

`unless` 関数は `when` 関数の真偽値を逆にしたものです。

```haskell
import Control.Monad (unless, when)

f :: Int -> IO ()
f x = do

    do
        when (x == 0) $ do
            putStr "Bar " :: IO ()
        putStrLn "Foo" :: IO ()

    do
        unless (x /= 0) $ do
            putStr "Bar " :: IO ()
        putStrLn "Foo" :: IO ()

main :: IO ()
main = do

    f 0
    f 23
```

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

条件によってモナドの計算を中断することもできます。

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

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

f :: Int -> IO ()
f x = do

    -- IO
    do
        when (x == 0) $ do
           throwIO $ userError "Error" :: IO ()
        print x :: IO ()
      `catch` \e -> do
        hPrint stderr (e :: IOError)

    do
        when (x == 0) $ do
           errorWithoutStackTrace "Error" :: IO ()
        print x :: IO ()
      `catch` \e -> do
        hPrint stderr (e :: ErrorCall)

    -- Maybe
    print $ do
        when (x == 0) Nothing :: Maybe ()
        Just x :: Maybe Int

    -- []
    print $ do
        when (x == 0) [] :: [()]
        [x, 42] :: [Int]

    -- Either
    print $ do
        when (x == 0) $ do
            Left "Error" :: Either String ()
        Right x :: Either String Int

main :: IO ()
main = do

    f 0
    f 23
```

```text:実行結果
user error (Error)
Error
Nothing
[]
Left "Error"
23
23
Just 23
[23,42]
Right 23
```

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

# 3. `Alternative`: `guard`

`guard` 関数は引数の値が真なら計算を継続し、偽なら計算を中断します。

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

f :: Int -> IO ()
f x = do

    -- IO
    do
        guard $ x /= 0
        print x :: IO ()
      `catch` \e -> do
        hPrint stderr (e :: IOError)

    -- Maybe
    print $ do
        guard $ x /= 0
        Just x :: Maybe Int

    -- []
    print $ do
        guard $ x /= 0
        [x, 42] :: [Int]

main :: IO ()
main = do

    f 0
    f 23
```

```text:実行結果
user error (mzero)
Nothing
[]
23
Just 23
[23,42]
```

※ `Either` 型は `Alternative` クラスのインスタンスでないため `guard` 関数は使えません。
※ `guard` 関数の代わりにパターン内のガードで `| otherwise` を書かないと部分関数になり、実行時に「Non-exhaustive (網羅していない)」エラーが発生します。

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