1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-10-26

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

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

0. まとめ

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

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

型クラス制約別:

  • Applicative
    • when, unless
  • Alternative
    • guard

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

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

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

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

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

pure () *> x = x

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

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
実行結果
Foo
Just 23
Just 23
[23,42]
[23,42]
Right 23
Right 23

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

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
実行結果
Bar Foo
Bar Foo
Bar Foo
Bar Foo
Foo
Foo
Foo
Foo

2. Applicative: when, unless

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

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

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
実行結果
Bar Foo
Bar Foo
Foo
Foo

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

参考「[Haskell] モナドの計算を中断する - Qiita

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
実行結果
user error (Error)
Error
Nothing
[]
Left "Error"
23
23
Just 23
[23,42]
Right 23

参考「when - Control.Monad
参考「unless - Control.Monad

3. Alternative: guard

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

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
実行結果
user error (mzero)
Nothing
[]
23
Just 23
[23,42]

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

参考「guard - Control.Monad

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?