fugafuga.write

日々のログ

すごいH本 part73

アプリカティブファンクターを使う

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 が使える

puref がアプリカティブファンクターになるもの。 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

と同じ。

これが、

  1. (Just (+) <*> Just 3) <*> Just 5
  2. Just (3+) <*> Just 5
  3. Just (3+5)
  4. Just 8

こうなる。

これの何が嬉しいのかと言うと、 普通の関数に対してアプリカティブ値を渡すことができるようになる。

pure f <*> xfmap 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]

所感

文脈があることによって表現力が上がる。 でもって、その文脈を保ったまま計算できるようになると嬉しい。 ということか。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

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