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

ファンクターとは

(工事中 50%)

はじめに

型の種類(kind)を思い出してみよう。*, * -> *, * -> * -> *, ...などがある。IntやChar、Boolなどは種類*の型だ。それに対して[]やMaybeなどは種類* -> *の型だ。つまり種類*の型であるIntやBoolなどを引数にとって

などのようにして使う。型クラスFunctorは[]やMaybeなどの種類が* -> *であるような型に対する型クラスである。

関数map

ここまで学んできたら、もうすでに関数mapについてはよくわかっていると思う。

% ghci
Prelude> map (* 10) [3, 5, 8, 2]
[30,50,80,20]
Prelude> :m Data.Char
Prelude Data.Char> map toUpper "Haskell"
"HASKELL"

リストの中身の各要素に関数を適用している。

関数mmap

various_maps.hs

Maybe値は要素数が0または1のリストと考えることもできる。そうするとMaybe値に対するmapとして以下のような関数が定義できる。

mmap :: (a -> b) -> Maybe a -> Maybe b
mmap f (Just x) = Just $ f x
mmap _ _ = Nothing

ghci functor.hs
*Main> mmap (* 10) (Just 8)
Just 80
*Main> mmap (* 10) Nothing
Nothing

関数tmap

Tree型の値についてもmapと同じような関数が定義できる。木の形は変えずにすべての要素に関数を適用する。

import Data.Tree

tmap :: (a -> b) -> Tree a -> Tree b
tmap f (Node x sf) = Node (f x) $ map (tmap f) sf

Nodeの第1引数は要素であり第2引数は木のリストである。tmap fはひとつの木の要素すべてに関数fを適用する関数だ。よってmap (tmap f)はリストに含まれるすべての木の要素すべてに関数fを適用する。

*Main> :reload
*Main> tmap (* 10) $ Node 3 [Node 4 [], Node 5 []]
Node {rootLabel = 30, subForest = [Node {rootLabel = 40, subForest = []}, Node {rootLabel = 50, subForest = []}]}

木の構造は変わらずになかの値だけ10倍になっているのがわかる。

型クラスFunctor

リスト、Maybe値、木の3つにそれぞれ関数map、mmap、tmapを定義した。これらはどれもコンテナの構造を変えずになかの値すべてに同じ変換を行う関数だ。型は以下のようになっている。

map :: (a -> b) -> [] a -> [] b
mmap :: (a -> b) -> Maybe a -> Maybe b
tmap :: (a -> b) -> Tree a -> Tree b

これらの共通の枠組みをとりだすと以下のようになる。

(a -> b) -> f a -> f b

このようなmap関数を定義できるような性質を型クラスFunctorとして抽象できる。

class Functor f where
fmap :: (a -> b) -> f a -> f b

ファンクタ則

残念ながら型だけですべてが表現できるわけではない。Functorクラスの値が本当にファンクタであるためには以下の法則を満たす必要がある。

これはfmapがコンテナの構造を変化させないことを保証する(ここ厳密に証明できるか?)。ひとつめは、関数idをすべての値に適用した場合に全体の値が変化しないということだ。ふたつめは、全体に関数fとgを合成することと、「全体に関数fを適用」と「全体に関数gを適用」することが同じであることを示す。この法則はfmapがコンテナの構造を変化させなければ成り立つはずだ。

関数fmapの型から導き出される性質を使うと、エラーなど特別な場合以外ではひとつめの規則からふたつめの規則を示すことができる。よって実際にはひとつめの規則だけを気にすればいい。

リストについて確認

リストでは関数fmapは以下のように定義される。

fmap = map

関数mapは以下のように定義される。

map f (x : xs) = f x : map f xs
map _ _ = []

fmap id == id

ファンクタ則のひとつめを見てみよう。引数が空リストのとき

map id [] == id []
=> [] == []

となり成り立つ。また、map id xs == id xsのとき

map id (x : xs)
=> id x : map f xs
=> x : map f xs
=> x : id xs
=> x : xs

となる。数学的帰納法によりmap id xs == id xsはすべてのxsで成り立つ。

fmap (f . g) == fmap f . fmap g

引数が空リストのとき

map (f . g) [] == map f (map g [])
=> [] == []

で成り立つ。map (f . g) xs == map f (map g xs)のとき

map f (map g (x : xs))
=> map f (g x : map g xs)
=> f (g x) : map f (map g xs)
=> f (g x) : map (f . g) xs
=> (f . g) x : map (f . g) xs
=> map (f . g) (x : xs)

数学的帰納法により任意のxsでmap (f . g) xs == map f (map g xs)は成り立つ。

コンテナでないようなFunctor

型クラスFunctorは中身すべてに同じ関数を適用できるようなコンテナとしての性質を表す。同様に関数fmapを中身すべてに同じ関数を適用する関数だ。しかし、実はこれは厳密には正しくない。コンテナであろうと何であろうとファンクタ則を満たすような関数fmapを定義できればそれは中身にかかわらずファンクタだ。

整数値に対する二項演算

functor_op.hs

コンテナでなくてもファンクタであるという例だ。整数値に対する二項演算を見ていこう。

型Opの定義

型クラスFunctorのインスタンスにするために型Int -> Int -> aをラップして型Opを作る。ラップを外すための関数opもあわせて定義する。

newtype Op a = Op { op :: Int -> Int -> a }

ファンクターにする

演算子に2個の引数をあたえたあとに関数を適用するという形で型Opをファンクターにする。

instance Functor Op where
fmap f (Op o) = Op $ \x y -> f $ x `o` y

ファンクター則を満たしていることを確認しよう。

fmap id (Op o)
=> Op $ \x y -> id $ x `o` y
=> Op $ \x y -> x `o` y
=> Op o
=> id (Op o)

fmap f (fmap g (Op o))
=> fmap f (Op $ \x y -> g $ o x y)
=> Op $ \x' y' -> f $ (\x y -> g $ o x y) x' y'
=> Op $ \x' y' -> f $ g $ o x' y'
=> Op $ \x' y' -> (f . g) $ o x' y'
=> fmap (f . g) (Op o)

よってファンクタ則を満たしている。型クラスFunctorのインスタンスでかつファンクタ則を満たしているので、型Opはファンクタである。

二項演算の例

二項演算をいくつか定義する。

add :: Op Int
add = Op (+)

comp :: Op Ordering
comp = Op compare

dividable :: Op Bool
dividable = (== 0) `fmap` Op mod

試してみる

定義した演算を試してみよう。

% ghci functor_op.hs
*Main> op add 3 4
7
*Main> op comp 3 8
LT
*Main> op dividable 8 3
False
*Main> op dividable 8 4
True

まとめ

「なかの要素すべてに値を適用できる」という性質を持つコンテナをファンクターとよぶ。ファンクター則を満たす関数fmapを定義して型クラスFunctorのインスタンスにする。ファンクターとなるのはコンテナだけではない。ファンクター則を満たす関数fmapを定義できれば何であれファンクターだ。

「もっとモノイド」へもどる 「ファンクターの使い道」へ

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