top page > computer > haskell > web_lecture > for_programmer > applicative_bak.html
更新日:
文責: 重城良国

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

(工事中 10%)

はじめに

ファンクターに対してたとえば以下のようなことができる。

fmap (* 15) foo

fooのなかみを15倍にしている。それではfooのなかみとbarのなかみを足し合わせることはできるだろうか。まずは以下のようにしてみよう。

fmap (+) foo

結果の型は以下のようになるだろう。fooの型をFoo Intとする。

Foo (Int -> Int)

Int -> Int型の関数をFoo Int型の値に適用するのが関数fmapの働きだ。しかし、Foo (Int -> Int)型の値に含まれる関数をFoo Int型の値に適用することはできない。そこで以下のような関数appを定義する。

app :: Foo (a -> b) -> Foo a -> Foo b

関数fmapの代わりにこの関数を使うことを考えてみよう。はじめは関数はFooのなかに入っていないので、以下のような関数を定義してFooにはいっていない関数をFooに入れる必要がある。

pure :: a -> Foo a

この関数を使うとfooのなかみとbarのなかみを足し合わせるのは以下のようにできる。

app (app (pure (+)) foo) bar

中置記法にするこ読みやすくなる。

pure (+) `app` foo `app` bar

このようにすると関数(+)の引数としてfooとbarを与えている感じが出る。

型クラスApplicative

複数のコンテナに対して多引数関数を適用するためには以下の2つがあればいい。

pure :: a -> Foo a
app :: Foo (a -> b) -> Foo a -> Foo b

関数appは中置記法のほうがコードがわかりやすくなるので最初から演算子として定義する。

pure :: a -> Foo a
(<*>) :: Foo (a -> b) -> Foo a -> Foo b

アプリカティブファンクターであれば以下のような定義で必ずファンクターにもなる。

fmap = (<*>) . pure

よって型クラスApplicativeのクラス宣言にはFunctorクラスのクラス制約をつける。

class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b

ファンクター

関数pureと(<*>)から関数fmapを定義できる。

fmap f = (pure f <*>)

さらに簡潔にすると

fmap = (<*>) . pure

関数fmapを実装としては必ずしもこのように定義する必要はない。しかし、関数の働きとしては同じでなければならない。このことを以下のように表現する。

fmap == (<*>) . pure

関数fmapはファンクタ則を満たす必要があることから

pure id (<*>) == id

でなければならない。

モノイダル則

メモ: 「モノイダル則」という言葉は一般的ではない。ここでは仮に「モノイダル則」と呼んでおくといったただし書きが必要だ。

アプリカティブにも満たさなければならない規則がある。アプリカティブ則は直観的にわかりにくいのでよりわかりやすいモノイダル則を示す。ファンクタ則とモノイダル則とからアプリカティブ則を示すことができ、逆にアプリカティブ則からファンクタ則とモノイダル則とを導き出せる。

まず以下の値や関数を考える。

unit :: Applicative f => f ()
unit = pure ()

(.**) :: Applicative f => f a -> f b -> f (a, b)
u .** v = pure (,) <*> u <*> v

値unitは「基本となる構造」を示す。演算子(.**)は値を含む2つの構造をひとつにまとめる働きをする。このとき、以下の規則を成り立たせる必要がある。

fmap snd, fmap fst, fmap aslの部分は中身についてそれぞれ((), y)をyに(x, ())をxに(x, (y, z))を((x, y), z)に変換している。ユニット値とのタプルをとることや、ペアの位置を変えることをトリビアルな変換と考え、それらの前後の値を同型と考えて(===)で表現すると

となり、これはモノイド則となっている。

アプリカティブ則

ファンクター則と同じようにアプリカティブにも型では強制できないが満たす必要のある規則がある。

アプリカティブがファンクタ則とモノイダル則を満たすための必要十分条件は以下のようになる。

これらを満たすアプリカティブを作ればファンクタ則やモノイダル則を満たすようになる。

この規則は関数pureと(<*>)がアプリカティブ則を満たし、関数fmapがファンクタ則を満たしさえすれば成り立つ。

リスト

たとえば[1, 2, 3]と[40, 50]の中身を足し算するとする。結果はどうなるだろうか。自然に考えて2通りの結果が考えられる。

すべての組み合わせで足し算を行うか、対応する位置の値どうしを足し算するかだ。Haskellではひとつめのやりかたがデフォルトとなっている。

instance Applicative [] where
pure = (: [])
fs <*> xs = concat $ map (\f -> map f xs) fs

以下の例で考えてみよう。

[(+ 10), (+ 20), (+ 30)] <*> [1, 2]
=> concat $ map (\f -> map f [1, 2]) [(+ 10), (+ 20), (+ 30)]

まず仮引数fに(+ 10)が入る。map (+ 10) [1, 2]なので[11, 12]となる。同素に(+ 20), (+ 30)についても計算されて[[11, 12], [21, 22], [31, 32]]となる。最後にconcatでまとめられて[11, 12, 21, 22, 31, 32]となる。

ファンクタ則

pure id xs
=> concat $ map (\f -> map f xs) [id]
=> concat $ [map id xs]
=> map id xs
=> xs

モノイダル則

まずは値unitと演算子(.**)を定義する。

unit
=> pure ()
=> [()]

xs .** ys
=> pure (,) <*> xs <*> ys
=> concat (map (\f -> map f xs) [(,)]) <*> ys
=> concat [map (,) xs] <*> ys
=> map (,) xs <*> ys
=> concat $ map (\f -> map f ys) (map (,) xs)
=> concat $ map ((\f -> map f ys) . (,)) xs
=> concat $ map (\x -> map (x ,) ys) xs
=> concat $ (`map` ys) . (,) `map` xs

[(x, y) | x <- xs, y <- ys]

モノイダル則の確認をする。

fmap snd $ unit .** ys
=> map snd $ concat $ (`map` ys) . (,) `map` [()]
=> map snd $ concat $ map (\x -> map (x ,) ys) [()]
=> map snd $ concat $ [map (() ,) ys]
=> map snd $ map (() ,) ys
=> ys

fmap fst $ xs .** unit
=> map fst $ concat $ map (\x -> map (x ,) [()]) xs
=> map fst $ concat $ map (\x -> [(x, ())]) xs
=> map fst $ map (\x -> (x, ()) xs
=> map fst $ map (, ()) xs
=> xs

fmap asl $ xs .** (ys .** zs)
=> map asl $ concat $ map (\x -> map (x ,) (ys .** zs)) xs
=> map asl $ concat $ map (\x -> map (x ,) (concat $ map (\y -> map (y ,) zs) ys) xs

fmap asl $ xs .** (ys .** zs)
=> [ asl (x, yz) | x <- xs, yz <- [ (y, z) | y <- ys, z <- zs ] ]

?=> [ asl (x, (y, z)) | x <- xs, y <- ys, z <- zs ]

(xs .** ys) .** zs
=> concat $ map (\xy -> map (xy ,) zs) $ xs .** ys
=> concat $ map (\xy -> map (xy ,) zs) $ concat $ map (\x -> (x ,) ys) xs
=> concat $ map (map

=> [ (xy, z) | xy <- [ (x, y) | x <- xs, y <- ys ], z <- zs ]

?=> [ ((x, y), z) | x <- xs, y <- ys, z <- zs ]

map f . concat . map (map g) == concat . map (map f . g)

メモ

以下をさきに証明しておくべきかも。

[ f x y | ..., (x, y) <- [ (x, y) | x <- xs, y <- ys ], ... ]
<=> [ f x y | ..., x <- xs, y <- ys, ... ]

リスト(2)

リストをアプリカティブにする方法のふたつめについてはラッパー型に対して定義されている。

newtype ZipList a = ZipList [a]

定義は以下のようになる。

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

これに対する関数fmap、値unitと演算子(.**)とを考えてみよう。

fmap f xs
=> pure f <*> xs
=> zpiWith ($) (repeat f) xs
=> map f xs

unit
=> pure ()
=> repeat ()

xs .** ys
=> pure (,) <*> xs <*> ys
=> zipWith ($) (zipWith ($) (repeat (,)) xs) ys
=> zipWith ($) (map (,) xs) ys
=> zipWith (,) xs ys
=> zip xs ys

これらがファンクタ則とモノイダル則を満たすことを確認する。

fmap id
=> map id
=> id

fmap snd $ unit .** ys
=> map snd $ zip (repeat ()) ys
=> ys

fmap fst $ xs .** unit
=> map fst $ zip xs (repeat ())
=> xs

fmap asl $ xs .** (ys .** zs)
=> map asl $ zip xs (zip ys zs)
=> zip (zip xs ys) zs

型Maybe

型ZipList

整数に対する二項演算

予定

アプリカティブ則ではなくモノイダル則のほうを説明するのもひとつの方法かもしれない。

class Functor f => Monoidal f where
unit :: f ()
(.**) :: f a -> f b -> f (a, b)

...

「1から学ぶHaskell」トップへもどる 「1から学ぶHaskell」トップへ

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics