アプリカティブファンクターを使う
2引数関数でファンクター値を写すとどうなるか
*Main> :t fmap (*) (Just 3) fmap (*) (Just 3) :: Num a => Maybe (a -> a) *Main> :t fmap compare (Just 'a') fmap compare (Just 'a') :: Maybe (Char -> Ordering) *Main> :t fmap (\x y z -> x + y / z) [3,4,5,6] fmap (\x y z -> x + y / z) [3,4,5,6] :: Fractional a => [a -> a -> a]
関数がファンクター値の中に入っている。
Just ((*) 3)
これらの使い道は、その中身の関数を引数に取れるような型を持つ関数を fmap すること。
*Main> let a = fmap (*) [1,2,3,4] *Main> :t a a :: Num a => [a -> a] *Main> fmap (\f -> f 9) a [9,18,27,36] *Main> fmap ($9) a [9,18,27,36]
例えば、ファンクター値 Just (3 *)
と ファンクター値 Just 5
があったとして、
Just (3 *)
から関数を取り出して Just 5
の中身に適用したい場合、
普通のファンクターではできない。
普通のファンクターは、通常の関数
で ファンクターの中の値
を写すことしかできない。
Applicative
Control.Applicative
にある型クラス Applicative
をみる
class Functor f => Applicative (f :: * -> *) where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
Applicative
のインスタンスになりたい型は Functor
のインスタンスである必要がある。
よって、常に fmap が使える
pure
の f
がアプリカティブファンクターになるもの。
pure
は任意の型の引数を取り、それをアプリカティブ値の中に入れて返す。
アプリカティブ値は箱というより文脈。
<*>
は fmap の強化版。
関数の入っているファンクター値と値の入っているファンクター値をとって、
1つ目のファンクターの中身の関数を2つ目のファンクターの中身に適用する。
Maybe はアプリカティブファンクター
instance Applicative Maybe where pure = Just Just f <*> m = fmap f m Nothing <*> _m = Nothing
使ってみる
*Main> Just (+3) <*> Just 9 Just 12 *Main> pure (+3) <*> Just 10 Just 13 *Main> pure (+3) <*> Just 9 Just 12 *Main> pure (++"hahah") <*> Nothing Nothing *Main> Nothing <*> Just "woot" Nothing
pure (+3)
と Just (+3)
の効果は全く同じだが、
pure
を使うのは、Maybe を Applicative として使う場合だけにし、
その他は Just を使うのが無難。
アプリカティブスタイル
Applicative 型クラスでは <*>
を連続して使うことができる。
これによって、複数のアプリカティブ値を組み合わせることができる。
*Main> pure (+) <*> Just 3 <*> Just 5 Just 8 *Main> pure (+) <*> Just 3 <*> Nothing Nothing *Main> pure (+) <*> Nothing <*> Just 5 Nothing
<*>
は左結合なので、
(pure (+) <*> Just 3) <*> Just 5
と同じ。
これが、
(Just (+) <*> Just 3) <*> Just 5
Just (3+) <*> Just 5
Just (3+5)
Just 8
こうなる。
これの何が嬉しいのかと言うと、 普通の関数に対してアプリカティブ値を渡すことができるようになる。
pure f <*> x
と fmap f x
は等しい。
これはアプリカティブ則の1つである。
pure
で値をデフォルトの文脈の中に入れ、
その文脈の中から関数を取り出して、別のアプリカティブファンクターの中の値に適用する。
これは、元の関数でアプリカティブファンクターを写すのと同じ。
なので、
pure f <*> x <*> y <*> ...
と書く代わりに、
fmap f x <*> y <*> ...
と書くことができる。
このパターンは頻出するので、Control.Applicative
の中に <$>
が定義されている。
(<$>) :: Functor f => (a -> b) -> f a -> f b (<$>) = fmap
これを使うと 関数 f を3つのアプリカティブ値の引数に適用したい場合、
f <$> x <*> y <*> z
と書ける。
*Main> (++) <$> Just "johntra" <*> Just "volta" Just "johntravolta" *Main> (++) "johntra" "volta" "johntravolta"
リストもアプリカティブファンクター
リスト型コンストラクタの []
もアプリカティブファンクターである。
instance Applicative [] where pure x = [x] fs <*> xs = [f x | f <- fs, x <- xs]
pure
はデフォルトの文脈に値を入れるもの。
ここで言う文脈とは、なるべく小さな、それでいて引数を再現できるような最小限の文脈のこと。
*Main> pure "hey" :: [String] ["hey"] *Main> pure "hey" :: Maybe String Just "hey"
<*>
が返すリストは、左辺のリストの中の関数を右辺のリストの中の値にあらゆる可能な組み合わせで適用したものが入る。
*Main> [(*0),(+100),(^2)] <*> [1,2,3] [0,0,0,101,102,103,1,4,9]
2引数関数のリストがあれば、その関数を2つのリストに適用できる。
*Main> [(+),(*)] <*> [1,2] <*> [3,4] [4,5,5,6,3,4,6,8]
100や"what"のような値は答えが1つしかない決定性計算であるのに対し、 リストはどの答えがいいのかきめられないので可能性のある答えを全て提示している。 とみなせる。
アプリカティブスタイルを使うことによって、リスト内包表記を置き換えることができる。
*Main> [ x*y | x <- [2,5,10], y <- [8,10,11]] [16,20,22,40,50,55,80,100,110] *Main> (*) <$> [2,5,10] <*> [8,10,11] [16,20,22,40,50,55,80,100,110]
アプリカティブスタイルの方がわかりやすい。
2つのリストの要素をかけ合わせて作れる数のうち 50 より大きなものすべてを求める
*Main> filter (>50) $ (*) <$> [2,5,10] <*> [8,10,11] [55,80,100,110]
所感
文脈があることによって表現力が上がる。 でもって、その文脈を保ったまま計算できるようになると嬉しい。 ということか。

- 作者: MiranLipovaca
- 出版社/メーカー: オーム社
- 発売日: 2017/07/14
- メディア: Kindle版
- 購入: 4人 クリック: 9回
- この商品を含むブログを見る