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 (網羅していない)」エラーが発生します。