関数をコンテナに適用するApplicativeクラス

 ただ,この例のように,持ち上げた関数を値に適用するためだけにMonadを使うのは大げさです。そこで,「値を持ち上げてコンテナに包む機能」と「持ち上げた関数を『コンテナに包まれた値』に適用する機能」を提供するのがApplicative(Applicative Functor)です。日本語では,適用可能函手(関手)や作用ファンクタとも呼ばれます。

 Applicativeは,Control.Applicativeモジュールで提供されている「Applicativeクラス」として定義されています。ApplicativeクラスはFunctorクラスを継承するクラスで,値を持ち上げるためのpure関数と,<$>演算子などで持ち上げた関数を「コンテナに包まれた値」に適用するための<*>演算子を提供します。

class Functor f => Applicative f where
    -- | Lift a value.
    pure :: a -> f a

    -- | Sequential application.
    (<*>) :: f (a -> b) -> f a -> f b

    -- | Sequence actions, discarding the value of the first argument.
    (*>) :: f a -> f b -> f b
    (*>) = liftA2 (const id)

    -- | Sequence actions, discarding the value of the second argument.
    (<*) :: f a -> f b -> f a
    (<*) = liftA2 const

 ap関数と<*>演算子の定義を比べると,これら二つの関数の違いは「文脈がApplicativeクラスであるかMonadクラスであるか」だけであることがわかります。

Prelude Control.Monad> :t ap
ap :: Monad m => m (a -> b) -> m a -> m b
Prelude Control.Monad> :m Control.Applicative
Prelude Control.Applicative> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b

 pure関数と<*>演算子に加えて提供されている*>演算子や<*演算子は,Functorの<$演算子と同様に補助関数です。これらの演算子にはデフォルトの定義が存在するため,Functorの<$演算子と同じように,Applicativeクラスのインスタンスを定義する際にこれらのメソッドを定義する必要はありません。

 *>演算子や<*演算子の定義に利用されているliftA2関数は,<*>演算子やFunctorクラスの<$>演算子を使って定義されています。

-- | Lift a binary function to actions.
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2 f a b = f <$> a <*> b

 Control.Applicativeモジュールでは,ほかにもliftAN(Nは数字)という名前の補助関数がいくつか提供されています。ですが,これらの関数は必ずしも必要ではありません。liftMN関数がliftM関数とap関数を使って定義できるのと同様に,ap関数に相当する<*>演算子を使うことで任意の数NのliftAN関数を定義できるからです。実際に,Control.Applicativeモジュールで提供されているliftAN関数は,<*>演算子を使って定義されています。

-- | Lift a function to actions.
-- This function may be used as a value for `fmap` in a `Functor` instance.
liftA :: Applicative f => (a -> b) -> f a -> f b
liftA f a = pure f <*> a

~ 略 ~

-- | Lift a ternary function to actions.
liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d
liftA3 f a b c = f <$> a <*> b <*> c

 では,Applicativeクラスの使用例を見てみましょう。これまでの回で説明した「Monadクラスのインスタンスである型」の多くは,Applicativeクラスのインスタンスとしても定義されています。

Prelude Control.Applicative> :i Applicative 
~ 略 ~
instance Applicative [] -- Defined in Control.Applicative
instance Applicative ZipList -- Defined in Control.Applicative
instance Monad m => Applicative (WrappedMonad m)
  -- Defined in Control.Applicative
instance Applicative Maybe -- Defined in Control.Applicative
instance Applicative IO -- Defined in Control.Applicative
instance Applicative (Either e) -- Defined in Control.Applicative
instance Applicative ((->) a) -- Defined in Control.Applicative

 Applicativeクラスのインスタンスとして定義されている型は,Functorクラスのインスタンスとしても定義されています。このため,モナドを使わずにFunctorとApplicativeを使って計算を組み立てることができます。

Prelude Control.Applicative> (* 11) <$ print "string" <*> pure 11
"string"
121
Prelude Control.Applicative> (*) <$> pure 11 <*> pure 11 <* print "string"
"string"
121
Prelude Control.Applicative> (*) <$> pure 11 <$> print "string" *> pure 11
"string"
11

 このように,モナドではなくApplicativeを利用して,<$>演算子や<*>演算子などによる関数適用で計算を組み立てる手法を「Applicativeスタイル(Applicative Style)」と呼びます(参考リンク1参考リンク2)。モナドを使った処理の制御を必要とせず,複数の計算の合成だけで処理を記述できる場合には,Applicativeスタイルで書けるかどうか検討してみるとよいでしょう。

 なお,FunctorクラスやMonadクラスと同様に,Applicativeクラスのインスタンスを定義する場合にも,満たさなければならない以下の法則があります。

  1. pure id <*> v = v
  2. pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
  3. pure f <*> pure x = pure (f x)
  4. u <*> pure y = pure ($ y) <*> u
これらはそれぞれ,
  1. pureメソッドを使って持ち上げたid関数が,恒等関数として振る舞うこと
  2. pureメソッドを使って持ち上げた「.」演算子が,合成関数として振る舞うこと
  3. 関数fと値xを持ち上げ,<*>演算子を使って「持ち上げた関数f」を「持ち上げた値x」に適用した結果が,「fにxを適用した結果」を単純に持ち上げたものと同じになること
  4. $演算子を使うことで,演算の順序の交換(interchange)が可能であること(参考リンク1参考リンク2
を要求しています。

 これらの法則は,StateやIOなどのコンテナの外にある純粋関数を使いつつ,StateやIOのような作用を持ったコンテナに対する計算を意図通りに組み立てられることを保証するものです。モナド則を満たすようにインスタンスを定義することで,モナドでStateやIOのような作用を持ったコンテナを扱えるのと同様です。なお,Applicativeクラスのpureメソッドの名前は純粋関数に由来しています。

 加えて,FunctorクラスのインスタンスとApplicativeクラスのインスタンスの間には,以下の法則が成り立つ必要があります。

  1. fmap f x = pure f <*> x

 この法則は,「fmapメソッドで関数fを持ち上げてコンテナに適用すること」と「pureメソッドで関数fをコンテナの値として持ち上げてから,<*>演算子を使って値xに適用すること」が等価でなければならないことを示しています。このことは,Applicativeクラスのインスタンスを使ってFunctorクラスのインスタンスを定義できることも意味しています。第3回で説明したMonadクラスとFunctorクラスの間にある法則の場合と同様です。

 さらに,ApplicativeクラスのインスタンスがMonadクラスのインスタンスでもある場合には,Applicativeクラスのインスタンスは以下の法則を満たすように定義しなければなりません。

pure = return
<*>  = ap

 この法則は,Applicativeスタイルで書いた処理が,モナドを使って書いた場合と等価になることを保証するためのものです。

 では,本当に「Applicativeスタイルで書いた処理が,モナドを使って書いた場合と等価になる」かどうかを確かめてみましょう。第17回で説明したように,「return f `ap` x1 `ap` ... `ap` xn」の結果は「liftM f x1 x2 ... xn」の結果と同じです。liftM関数は以下のような形で定義されています。

-- | Promote a function to a monad.
liftM   :: (Monad m) => (a1 -> r) -> m a1 -> m r
liftM f m1              = do { x1 <- m1; return (f x1) }

-- | Promote a function to a monad, scanning the monadic arguments from
-- left to right.  For example,
--
-- >    liftM2 (+) [0,1] [0,2] = [0,2,1,3]
-- >    liftM2 (+) (Just 1) Nothing = Nothing
--
liftM2  :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 f m1 m2          = do { x1 <- m1; x2 <- m2; return (f x1 x2) }

 「pure = return」と「<*> = ap」が成り立てば,「pure f <*> x1 <*> ... <*> xn 」および「f <$> x1 <*> ... <*> xn」は「liftM f x1 x2 ... xn」と等しくなります。これは,do式を使って書いた「do { x1 <- m1; ... xn <- mn; return (f x1 ... xn)}」という式と同じです。つまり,Applicativeを使って書いた処理とモナドを使って書いた処理が等しくなることが保証されているのがわかります。

 また,この法則をそのまま利用することで,MonadクラスのインスタンスからApplicativeクラスのインスタンスを定義できます。

 では,Applicativeクラスのインスタンスの定義を見てみましょう(参考リンク)。

instance Applicative Maybe where
    pure = return
    (<*>) = ap

~ 略 ~
instance Applicative [] where
    pure = return
    (<*>) = ap

~ 略 ~
instance Applicative IO where
    pure = return
    (<*>) = ap

~ 略 ~
instance Applicative STM where
    pure = return
    (<*>) = ap

~ 略 ~
instance Applicative ((->) a) where
    pure = const
    (<*>) f g x = f x (g x)

~ 略 ~
instance Applicative (Either e) where
    pure          = Right
    Left  e <*> _ = Left e
    Right f <*> r = fmap f r

 「(->) a」型やEither型に対するApplicativeクラスのインスタンスを除いて,ApplicativeクラスのインスタンスとMonadクラスのインスタンスの間で成り立たなければならない「pure = return」と「<*> = ap」の二つの法則をそのまま使った定義になっています。

 「(->) a」型に対するインスタンスの定義でreturnメソッドやap関数が使われていないのは,このインスタンスにおけるpureメソッドが「Kコンビネーター(K combinator)」,<*>演算子が「Sコンビネーター(S combinator)」と呼ばれる組み合わせ子であることを明確に示すためでしょう(参考リンク)。

 Either型に対するインスタンスの定義でreturnメソッドやap関数が使われていないのは,歴史的な事情だと考えられます。第28回で見たように,以前のEither型に対するMonadクラスのインスタンスは,エラー・モナドの一つとしてmtlパッケージで定義されていました。このため,returnメソッドやap関数を使ってApplicativeクラスのインスタンスを定義することができなかったのでしょう。

 ただし,GHC 7.0.1以降では,Either型はエラー・モナドから切り離されました。Errorクラスの制約を持たないEither型に対するインスタンスが,baseパッケージのControl.Monad.Instancesモジュールで定義されています(参考リンク)。将来は,returnメソッドとap関数を使った定義に変更されるかもしれません。