Chapter 29

7.3 関手の合成

さのたけと
さのたけと
2021.05.31に更新

集合と集合の間の関数に合成があるように,圏と圏の間の関手にも合成がありそうだというのは想像に難くない.関手の合成とは,対象については対象間のマッピングの合成であり,射についても同様である.さらに,恒等関手同士の合成が恒等関手で,射と射の合成が射の合成になる.特別なことは何もない.自己関手の合成は特に簡単だ. maybeTail を覚えているだろうか? リスト版の maybeTail を Haskell のビルトインの実装を使って書いてみる.

maybeTail :: [a] -> Maybe [a]
maybeTail :: [] = Nothing
maybeTail :: (x:xs) = Just xs

(我々が Nil と呼んでいる空リストのコンストラクタは空(から)の大かっこ [] に置き換えてある.Cons コンストラクタは中置演算子 : (コロン)に置き換わる.)maybetail の結果は, Maybe[] の合成を a に作用させたものになる.これらの関手はそれぞれ fmap を持っているが,合成した Maybe リストに対してある関数 f を作用させるにはどうしたらよいだろう? f は二つの関手の壁を越えねばならない.f 自身はリストに対して機能しないので, Maybe の中に f を送り込むだけではうまくいかない. (fmap f) をリストの内側にまで送り込む必要がある.たとえば, 整数の Maybe リストの要素を二乗するにはどうすればよいか見てみよう.

square x = x * x
mis :: Maybe [Int]
mis = Just [1, 2, 3]

mis2 = fmap (fmap square) mis

コンパイラは型解析をして,外側の fmapMaybe インスタンスの実装を,内側の fmap はリスト関手の実装をつかえばよいと判断する.すこしわかりにくいかもしれないが,先のコードが次のように書き換えられる.

mis2 = (fmap . fmap) square mis

ここで, fmap は次のように引数1個の関数とみることができたのを思い出そう.

fmap :: (a -> b) -> (f a -> f b)

この見方を使うと, (fmap . fmap) の二個目の fmap は,

square :: Int -> Int

を引数にとり,

[Int] -> [Int]

という関数を返していると見ることができる.これが一つ目の fmap に渡されて,次の関数が得られる.

Maybe [Int] -> Maybe [Int]

最終的に,この関数が mis に作用する.したがって二つの関手の合成は関手であって,その fmap は元の二つの関手の fmap の合成になる.話を戻して,圏論の言葉に言い換えると,関手の合成は結合的であるということだ.(つまり対象のマッピングと,射のマッピングの両方が結合的である.)さらに,基本的な関手である恒等関手はすべての圏に存在する.恒等関手とはすべての対象をそれ自身にマッピングし,すべての射もそれ自身にマッピングされるというものだ.そう考えると,関手は射としての性質を持っている.だとすると,関手を射とする圏とはどんなものなのだろう? それは,対象が圏で射が関手という圏で,いわば圏の圏だ.しかしすべての圏の圏には自分自身も含まれるはずで,すべての集合の集合が作れないのと同じような矛盾を生んでしまう.そこで少し限定して,すべての 小さい圏 (small categoris) の圏 \mathbf{Cat} を考えてみよう.(\mathbf{Cat} 自体は大きい圏で,自身の要素にはなれないとする.)小さい圏とは,対象が集合であるものをさし,集合より大きいものは含まないとする. 覚えておいて欲しいのは,圏論では無限非可算集合も「小さい」ということだ.あえてこう言ったのは,抽象化のいろいろな階層で自分自身を繰り返すような構造が出てくることに,筆者自身がとても驚いたからだ.関手が圏をなす例についてはこの後で見ていこう.

(和訳:@takase