Applicativeを使ってみる


ということで、まだまだ先はありますが、Applicativeの使い方が少し見えてきました。

mapでこうやることが:
map (+ 1) [1, 2, 3] = [2, 3, 4]

Applicativeだとこうできる:
pure (+1) <*> [1, 2, 3] = [2, 3, 4]

いくつのリストを対象に関数を適用するかで、map = 1, zipWith = 2, zipWith3 = 3,...と使う関数を変えていかなくてはならないのですが、Applicativeであれば、pureと<*>だけで何個リストがあっても大丈夫...これはきれいです。

例えば、パラメタを2つとる+をリスト2つに適用するには、zipWithでは:

zipWith (+) [1, 2, 3] [2, 3, 4] = [3, 5, 7]

ちなみに、mapでもたしょうApplicativeなことはできます…

zipWith (id) (map (+) [1, 2, 3]) [2, 3, 4] = [3, 5, 7]

論文どおりに行けば、Applicative版は次のようになるのですが:

pure (+) <*> [1, 2, 3] <*> [2, 3, 4] = [3,4,5,4,5,6,5,6,7] --- !!!

なんだか変な結果が出てきました。ちょっとびっくりです。あわててリストのApplicative定義を見に行くと:

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

ap :: Monad m => m (a -> b) -> m a -> m b
ap                :: (Monad m) => m (a -> b) -> m a -> m b
ap                =  liftM2 id

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)}

instance  Monad [ ]  where
    m >>= k             = foldr ( (++) . k) [ ] m
    m >> k              = foldr ( (++) . (\ _ -> k)) [ ] m
    return x            = [x]
    fail _		= [ ]

となっていました。pure x = return x = [x]となっているように、論文であげられていたリストのApplicative定義とはちょっと違います。<*>もモナドメソッドのapをそのまま使っているし...色々調べてみると、どうやら、Control.ApplicativeではリストのApplicative定義はリストコンプリヘンションの表記と同じような振る舞いをしているようです。

pure (+) <*> [1, 2, 3] <*> [2, 3, 4]
	= [+] <*> [1, 2, 3] <*> [2, 3, 4]
	= [1 +, 2+, 3+] <*> [2, 3, 4]
	= [1 + 2, 1 + 3, 1, + 4, 2 + 2, 2 + 3, 2 + 4]
	= [3,4,5,4,5,6,5,6,7]

ということで、<*>の左辺と右辺が総当り的に適用されるようです。

さらに調べていくと、ZipListという型を見つけました:

newtype ZipList a = ZipList { getZipList :: [a] }

instance Functor ZipList where
	fmap f (ZipList xs) = ZipList (map f xs)

instance Applicative ZipList where
	pure x = ZipList (repeat x)
	ZipList fs <*> ZipList xs = ZipList (zipWith id fs xs)

こちらは論文で見かけたとおりの定義です。ということで、

z xs = ZipList xs

getZipList $ pure (+) <*> (z [1, 2, 3]) <*> (z [2, 3, 4])

   = [3, 5, 7] -- good!!!

getZipList$ (avg)<$>(z [1, 2, 3])<*>(z [2, 3, 4])<*>(z $repeat 2)
	where
		avg :: Int -> Int -> Int -> Int
		avg x y d = (x + y) `div` d

    = [1, 2, 3]

期待通りの結果が出ています。ちなみに2つ目の例はパラメタを3つとる関数をApplicativeで適用している例です…f <$> a はpure f <*> aのショートハンドです。

調べたところ、zipWithNは最大7までしか定義していないようなので、それ以上必要な人は、Applicativeお勧めですね…

面白いのは、例えば:

getZipList $ pure (+) <*> (ZipList [1, 2, 3]) <*> (ZipList [2, 3, 4]) = [3, 5, 7]

これを例にとると、一つ目の<*>の型は:
 (<*>) :: f (a -> a -> a) -> f a -> f (a -> a)
で、2つ目は
 (<*>) :: f (a -> a) -> f a -> f a
と、お互いに同じ型を扱っているわけではないところです。

Haskellでは:
a -> b -> c -> d
という型定義はaをパラメタとしてb -> c -> dを返す関数とも取れるし、a, b, cをパラメタとしてdを返す関数とも取れるという自由度がありますね...これはC言語などでは経験したことのない類の力をもたらしていますね…

ということで、Applicativeはmapとやっていることがとても似ていますが、パラメタの数に対する自由度はこの型の自由度からきているということでしょう。mapではここまで自由は利きませんね...ただし、Applicativeもパラメタの数にある程度柔軟に対応できるといっても、パラメタがいくつあるのかわからない状況(コンパイルされる時点で確定されていない状況、パラメタの数が違う関数がわたってくる状況)には対応できなさそうですね...

ほかにも、

newtype WrappedMonad m a = WrapMonad { 
	unwrapMonad :: (m a)
}

instance Monad m => Applicative (WrappedMonad m)

なんていうのがあって、モナドなら簡単にApplicativeの定義を手に入れることができるようです。もっとも、上のリストのようにWrappedMonadが必ずしも自分の使いたい動き方をしてくれるというわけではないようですが…

ZipListがデフォルトのリストのApplicativeのインプリメンテーションではない理由はきっとZipListはApplicativeではあるけど、モナドではないからということなんでしょうかね…

ではでは...