LoginSignup
7
1

More than 3 years have passed since last update.

関数型プログラミング入門〜モナドで分岐ができる有り難みを改めて確認しておこう〜

Posted at

関数型プログラミングでは、関数の適用や合成を繰り返し行うことでプログラムを作成します。
モナドは抽象的な概念なので、関数型プログラミングの学習初期はモナドの有り難みが分かりづらいです。
今回はMonadを使うと、特定の文脈を維持したまま分岐処理が実現できることを確認してみます。

Monad型クラスの定義

まずは、Monad型クラスの定義をさらっと見ておきましょう。

Monad型クラスの定義(抜粋)
class Applicative m => Monad (m :: * -> *) where
  return :: a -> m a
  (>>=) :: m a -> (a -> m b) -> m b

1行目の記述は以下のようになっています。

class Applicative m => Monad (m :: * -> *) where

上記の定義からMonad型クラスのインスタンスは、Applicative型クラスのインスタンスでもあることが分かります。あとで見るように、モナドは「return」と「(>>=)」という関数を持ち合わせていることから、MonadはApplicativeよりも強力であることが伺えます。
また、型変数mのカインドは「* -> *」ということも分かります。

  return :: a -> m a

returnの型定義は、Applicativeにおけるpureと一緒です。
ある値をMonadの文脈に持ち上げます。

Monad型クラスの(>>=)の型定義を見てみましょう。

(>>=) :: Monad m => m a -> (a -> m b) -> m b

(>>=)の第1引数のm aはmをモナドとするモナド値です。
第2引数は、具体的な値をとってm bという新たなモナド値を返す関数です。
最終的にm bの型の値を得ることができます。

モナドは文脈を維持したまま分岐処理ができる

モナドを使えば、特定の文脈を維持したまま、以前に実施した計算の結果に依存した分岐処理が実現できます。

FunctorとApplicativeにより、関数を特定の文脈に適用し使用することができました。
しかし、実はこれだけだと困ったことがあります。
直前の計算結果に依存した分岐処理がFunctorとApplicativeだけではできないのです。
どういうことか、Applicativeを使った例を見てみましょう。

Applicativeを使った例
-- User :: String -> Int -> User
data User = User 
  { userName :: String
  , userAge :: Int 
  } deriving (Show)

getName :: Maybe String                                                                                                                                                                                                                       
getName = Just "Taro" 

getAge :: Maybe Int
getAge = Just 20

getUserA :: Maybe User
getUserA = User <$> getName <*> getAge

main :: IO ()
main = do
  print getUserA -- Just (User {userName = "Taro", userAge = 20})が出力される
getUserA = User <$> getName <*> getAge
{-
 上記の記法に馴染みがない方は、以下の型を追って確認してみてください。

 (<$>) :: Functor f => (a -> b) -> f a -> f b
 (<*>) :: Applicative f => f (a -> b) -> f a -> f b
 User :: String -> Int -> User
 (User <$>) :: Functor f => f String -> f (Int -> User)
 (User <$> getName) :: Maybe (Int -> User)
 (User <$> getName <*>) :: Maybe Int -> Maybe User
 (User <$> getName <*> getAge) :: Maybe User
-}

上記で、Functorのfmap(<$>)関数とApplicativeの<*>関数を使用していますが、
getName関数とgetAge関数で得られた値を、そのまま適用しているだけであることに注目してください。

一方、Monad型クラスの(>>=)を使えばgetName/getAge関数で得られた値に応じて、処理を分岐することができます。

getAge関数で得られた値により処理を分岐する
getUserM =                                                                                                                                                                                                                                    
  getName >>= \name ->
  getAge >>= \age ->
  (if age > 10 then Just (age+10) else Nothing) >>= \newAge ->
  return $ User name newAge 

main = do
  print getUserA -- Just (User {userName = "Taro", userAge = 20})が出力される
  print getUserM -- Just (User {userName = "Taro", userAge = 30})が出力される

上記のgetUserM関数では、getAge関数で得られた値により最終的な計算結果を代えることができています。
改めて、getUserAとgetUserMを見比べてみると、Applicativeでは関数から得た値をそのまま適用しているだけで、分岐処理が行えないことが確認できます。

-- ApplicativeスタイルではgetName/getAgeで得られた値をそのまま適用するだけ
-- モナドよりも処理が制限されるので、分岐が不要な場合はこちらの記法が好まれたりする
getUserA :: Maybe User
getUserA = User <$> getName <*> getAge

-- do記法
getUserM :: Maybe User                                                                                                                                                                                                                        
getUserM = do                                                                                                                                                                                                                                 
  name <- getName
  age <- getAge
  if age > 20  -- 分岐処理ができる
    then Just $ User name (age+10)
    else Nothing

プログラムの作成において、分岐処理は欠かすことができません。
上記の例だけでも、Monad型クラスが提供する(>>=)がいかに強力か分かると思います。
今回は、Monadの側面の一つである分岐処理の実現について見てみました。

7
1
3

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
7
1