Applicativeを理解する
前回の記事ではFunctorについて紹介しました。今回はFunctorをさらに強力にしたApplicativeという型クラスについて解説します。
適用する関数も箱に入れる
Functorでは、ある状態という箱に入った値を関数に適用するための振る舞いを定義しているのでした。
ここで適用する関数も箱に入っていた場合を考えてみましょう。当然fmapでは箱に入った関数を取り出すことはできません。
> fmap (Just (+3)) (Just 3) <interactive>:44:7: Couldn't match expected type ‘Integer -> b’ ...
そこで登場するのがApplicativeです。Appliactiveは箱に入った値を箱に入った関数に適用する方法を知っています。
Applicativeとは
ApplicativeはControl.Applicativeに定義されています。
class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
http://hackage.haskell.org/package/base-4.9.1.0/docs/src/GHC.Base.html#Applicative
Applicativeには型制約が付いておりApplicativeのインスタンスになるためにはFunctorのインスタンスである必要があります。
今回、箱に入った値を箱に入った関数に適用するには(<*>)
を使います。この関数はfmapと型シグネチャが似ていますが、第一引数の関数がアプリカティブ値に入っています。Functorと同じくこの値には箱となる型が入ります。
では、Maybeを例に(<*>)
がどのような実装になっているのか見てみましょう。
instance Applicative Maybe where pure = Just Just f <*> m = fmap f m Nothing <*> _m = Nothing
http://hackage.haskell.org/package/base-4.9.1.0/docs/src/GHC.Base.html#line-656
実際に使ってみます。
> Just (+3) <*> Just 3 Just 6 > Nothing <*> Just 3 Nothing
Justに入った関数がきたときには、箱から関数を取り出してfmapを使って値に適用しています。値を関数に適用した後の箱に値を入れ直す処理はfmapを使えばFunctorが全てやってくれます。
さらに<*>
は連続して組み合わせることもできるので、はじめから箱に関数が入っていなくても計算することができます。pure関数を使って関数をアプリカティブ値で包むことで実現できます。
> :t pure (+) <*> Just 100 pure (+) <*> Just 100 :: Num a => Maybe (a -> a) > pure (+) <*> Just 100 <*> Just 3 Just 103
けれど<$>
を使うことでもっと簡単に書くことができます。
> :t (+) <$> Just 100 (+) <$> Just 100 :: Num a => Maybe (a -> a) > (+) <$> Just 100 <*> Just 3 Just 103
それと同じ処理をする便利な関数liftA2
もあります。
> :m Control.Applicative > liftA2 (+) (Just 100) (Just 3) Just 103
Applicativeのインスタンス
Functorのインスタンスである型ならどれでもApplicativeになれます。
その中でもリストは変わった結果を返します。
> [(^2),(+100)] <*> [1..5] [1,4,9,16,25,101,102,103,104,105]
リストアプリカティブファンクターは適用する関数が複数である可能性があるため、その全ての関数に対してそれぞれの値を適用した新しいリストを返します。この例では2*5=10通りの組み合わせのリストが返ってきます。このようにリストは複数の結果を取り得る非決定性計算を表現するのに使われます。
インスタンス定義は次のようになっています。<*>
はリスト内包表記で実装されています。
instance Applicative [] where pure x = [x] fs <*> xs = [f x | f <- fs, x <- xs]
http://hackage.haskell.org/package/base-4.9.1.0/docs/src/GHC.Base.html#line-739
Maybeのときと同じく<*>
は連続して使うことができます。
> [(^),(+)] <*> [2,10] <*> [1..5] [2,4,8,16,32,10,100,1000,10000,100000,3,4,5,6,7,11,12,13,14,15]
おっと、さっきの組み合わせよりも大きなリストができてしまいました。
左から順を追って見てみましょう。まずは2*2=4通りの組み合わせリストが返ります。
> let a = [(^),(+)] <*> [2,10] > :t a a :: Integral b => [b -> b] -- [(2^),(10^),(2+),(10+)]
そしてこの関数リストに[1..5]
を適用します。組み合わせは4*5=20通りです。
> a <*> [1..5] > :t a <*> [1..5] a <*> [1..5] :: Integral b => [b] -- [2,4,8,16,32,10,100,1000,10000,100000,3,4,5,6,7,11,12,13,14,15]