お布団宇宙ねこ

にゃーん

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]

参考文献