Help us understand the problem. What is going on with this article?

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

関数型プログラミングでは、関数の適用や合成を繰り返し行うことでプログラムを作成します。
モナドは抽象的な概念なので、関数型プログラミングの学習初期はモナドの有り難みが分かりづらいです。
今回は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の側面の一つである分岐処理の実現について見てみました。

masaki_shoji
ソフトウェアエンジニア。株式会社G・B・S所属。 仕事では主にAndroid開発を担当する事が多いですが、システム全般に興味があります。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした