Applicative

とりあえずいろんなモジュールを先に見とこうということで、Control.Applicativeについて調べました。

Applicativeは関数適用を抽象化したもののようです。

Applicative.hsでは主に以下の演算子が用意されています。*1

  • pure
  • <*>
  • <$>
  • <$
  • *>
  • <*
  • <**>
  • liftA
  • liftA2
  • liftA3

Applicativeのインスタンスになるために定義すれば良いのはpureと<*>です。
また、<$>と<$はApplicativeとは直接関係無く、Functorに属するデータに対して定義されます。

<$>, <$演算子

<$>は普通の関数をFunctorなコンテナ内のデータに適用します。

>(+ 1) <$> [1, 2, 3, 4]
[2,3,4,5]
>(+ 1) <$> Just 1
Just 2
>(+ 1) <$> Nothing
Nothing

実はIOもFunctorに属するのでこんなことも出来ます。

> take 10 <$> getContents   -- 標準入力から10文字読むIOモナドを生成
> head <$> getArgs          --  最初のコマンドライン引数を返すIOモナドを生成

例えば、

import Control.Applicative
main = take 10 <$> getContents >>= putStrLn

で標準入力から10文字読み、それを出力するプログラムになります。読みやすいかどうかは正直微妙ですね。<$はコンテナ内のデータを定数で置き換えます。

> 1 <$ [1, 2, 3, 4]
[1,1,1,1]
> 1 <$ Just 2
Just 1
> 1 <$ Nothing
Nothing

<*>演算子

<*>演算子は<$>と似ていますが、関数もコンテナに格納されている点が異なります。

>[(+ 1), (+ 2)] <*> [1, 2, 3, 4]
[2,3,4,5,3,4,5,6]
>Just (+ 1) <*> Just 2
Just 3
>Just (+ 1) <*> Nothing
Nothing
>Nothing <*> Just 2
Nothing
>Nothing <*> Nothing
Nothing

リストの例が一番おもしろいのではないかと思いますが、やはりリストモナドと同じく非決定計算を表現するために利用します。

pureは定数をコンテナに格納する為の関数です。

>[(+ 1), (+ 2)] <*> pure 1        -- 1 を [1]に
[2,3]
>Just (+ 1) <*> pure 1           -- 1 を Just 1に
Just 2

<$>と<*>の連携

Applicativeの面白いところがここからです。

先ほどの例を少し変形した下の例ですが、

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

(1 +)などの関数の部分適用は以下と同じですので

(+) 1 == (1 +)

[(1 +), (2 +)]を作りたかったら、[1, 2]というリストの各要素に(+)を適用すれば良いです。先ほどの<$>を使えば、これは

(+) <$> [1, 2]  ( ==  [(1 +), (2 +)] )

と書けます。
ということで最初の例は

>(+) <$> [1, 2] <*> [1, 2, 3, 4]
[2,3,4,5,3,4,5,6]

と書けることになります。
リスト内包表記を使えば、

> [x + y | x <- [1, 2], y <- [1, 2, 3, 4] ]

と同じです。
要するに総当たり計算を表現することが出来るわけですね。

3要素以上になっても同様です。

>(,,) <$> [1, 2] <*> ["foo", "bar", "baz"] <*> [True, False]
[(1,"foo",True),(1,"foo",False),(1,"bar",True),(1,"bar",False),(1,"baz",True),(1,"baz",False),(2,"foo",True),(2,"foo",False),(2,"bar",True),(2,"bar",False),(2,"baz",True),(2,"baz",False)]

Maybeモナドの場合は1つでもNothingがあったらNothingになるような計算になります。

>(+) <$> Just 1 <*> Just 2
Just 3
>(+) <$> Just 1 <*> Nothing
Nothing
>(+) <$> Nothing <*> Just 2
Nothing

liftA, liftA2, liftA3はこれらと同じ事を行う関数です。

>liftA2 (+) [1, 2] [1, 2, 3, 4]
[2,3,4,5,3,4,5,6]

*>と<*は<$と同様の定数関数を抽象化したもの、<**>は<*>と引数を逆にしただけのものです。
>[1, 2] <* [1, 2, 3, 4]
[1,1,1,1,2,2,2,2]

表にしてみました。

関数適用 定数関数適用
普通の関数 <$> <$
コンテナに格納された関数 <*> <*, *>

Applicativeとしての関数

通常の関数もApplicativeのインスタンスです。正確には((->) a)型のデータがApplicativeのインスタンスなのですが、これは入力が固定された関数だと思ってもらえば良いです。

んで、最初はこれが何なのかよく分かって無かったんですが、実はpureと<*>がそれぞれKコンビネータというものとSコンビネータに対応しているみたいです。(<*>はSコンビネータを中置形式で書けるようにしたもの...?)

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

unlambdaな人によればKコンビネータとSコンビネータがあれば基本的なプログラムは書けるらしいです(?)

Kコンビネータは定数を返す関数で、Sコンビネータは関数合成を抽象化したものらしいですが、<$や<*や*>などのよく使い道の分からない定数に置き換える演算子が用意されているのもこの辺りと通じるところがあるのかもしれません。こういう話はよくわからないのでもし詳しい方がいらっしゃいましたら、ぜひ解説をお願いします。


とりあえず<*>が引数を複製して、片方変形してくれるものだという浅い理解で一つコードを書いてみました。題材はどう書く?org 二項の差です。

import Control.Applicative
diff = zipWith subtract <*> tail
>diff [3, 1, 4, 1, 5, 9, 2, 6, 5]
[-2,3,-3,4,4,-7,4,-1]

Applicativeに属するデータは他にもいろいろありますが、よく使いそうなのは以上です。
これらの挙動をちゃんと理解しとくとTraversableでの使い方も分かってくるのかもしれません。考えてみます。

*1:Applicative.hs内には他にAlternativeというものも用意されていますが省略します