bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

関手―Haskellで関手をどのように理解したらよいか

6.6 Haskellで関手をどのように理解したらよいか

圏論の中で重要な概念の一つは関手(functor)だ。そして、Haskellでも最も便利な道具の一つはファンクタである。数回の記事の中で、いくつかの例を挙げながら、圏論での関手を説明してきた。また、それとのつながりの中で、Haskellでのファンクタの使い方についても説明してきた。だいぶ慣れてきたとは思うが、次のステップに行くために、ここでもう一度原点に立ち戻って関手とは何だったのか、ファンクタとは何だったのかをもう一度振り返ることにする。

まず、圏とはという話からしてみよう。圏は構造を持った世界である。構造は簡単に言ってしまうと点の集まりとその間を矢印で結んだものである。数学の用語を用いると点は対象であり、矢印は射である。点と矢印はとても抽象化された世界である。その実例はいろいろなものを考えることができる。

点は個々のものであっても構わないし、集まりであっても構わない。例えば、小学校での学級という世界を考えるときは、点は一人一人の生徒になるであろう。しかし、小学校という世界を考えるときは、点はそれぞれの学級になるであろう。対象が学級となった時は、それを構成している要素である個々の生徒については考えないということになる。さらに進んで、横浜市の小学校という世界になれば、点は小学校ということになり、学級でさえ無視されることとなる。

矢印は点の間の関係を表す。点が学級の場合であればどのような矢印が考えられるであろうか。朝礼の時の並び順は二人の児童の関係を表しているので矢印として利用することができそうだ。同じような意味で成績順なども矢印として利用できそうだ。しかし、圏では二つの矢印は合成できるという約束になっている。このため、二つの点の関係を表しているものでも矢印として利用できないものも存在する。例えば、好きや嫌いを表すような関係は難しい。A君がB君を好きで、B君がC君を好きだとしても、A君がC君を好きだとは限らない。往々にして嫌いなことがある(圏論では矢印はつなげられるのでA君はC君が好きでないと破綻してしまう)。このため、圏論を実際の場面に応用しようとした時、工夫をしないとうまく表せないものもある。

世界を点と矢印で構成したら、二つの圏の構成を比べてみたいと自然に思うようになるだろう。例えば、学級の世界と大人社会の世界を比較したいと思うだろう。学級での児童間での上下関係と大人社会での権力構造とを比較してみようと思うだろう。どうしたらできるだろうか。一つの考え方は、子供の上下関係を大人の権力構造に移すことだろう。うまく移すことができれば、学級でのある構造が大人社会にも内在しているということを発見するであろう。このような役割を担ってくれるのが関手である。

f:id:bitterharvest:20170216082517p:plain

関手をビジュアルに示したのが、上図である。図には二つの圏\(\mathcal{C,D}\)がある。それぞれの圏で、点で表されたのが対象であり、矢印で表されたのが射である。従って、\(\mathcal{C}\)では、点として表されている\(A,B,C\)が対象であり、矢印として表されている\(f,g\)が射である。また、\(f\)と\(g\)を合成した矢印\(g \circ f\)も射である。この他に自身への射、恒等射\(id_A,id_B,id_C\)が存在する。

\(\mathcal{C}\)の構造を移したのが、右側の\(\mathcal{D}\)である。こちらには\(\mathcal{C}\)の構造を移したものしか表示していない。このため、この他にも点や矢印は存在しても構わないものとする。

\(\mathcal{C}\)の構造を\(\mathcal{D}\)に写したものが関手\(F\)である。従って、\(A,B,C\)は\(F(A),F(B),F(C)\)に移される。また、\(f,g,g \circ f\)は\(F(f),F(g),F(g \circ f)\)に移される。さらに、関手には次のようは次のような約束を求めている。合成した射を関手で写像したものは、それぞれの射を関手で写像しそれを合成したものと同値である、即ち、\(F(g \circ f)=F(g) \circ F(f)\)である。即ち、関手で写像する前に合成しても関手で写像した後で合成したとしても同じであるという約束をしている。これは恒等射についても同じである。

関手の定義には、写像(射)を写像するという概念が入り込むので、最初はとっつきにくきもする。しかし、射を写像の集合と見なせば、これまで馴染んできた集合の写像の概念と同じになるのですんなりと受け入れられるようになる(圏では射の集合は\({\rm Hom}(A,B)\)で表す。これは、対象\(A\)から対象\(B\)への射の集合と読む)。

Haskellで関手をどのように利用したらよいのだろうか。Haskellを使っていて便利だなと思うのは、データ型を自由に設定できることだ。プログラムはある応用分野に向けて書くのが普通だ。会計のシステムを作りたいとか、量子力学の問題を解きたいとか、ゲームを作ってみたいとか、それぞれ目的としているところがあると思う。そして、それぞれの応用分野には、それぞれ独自の用語がある。あるいは、それぞれの用語がその世界を規定しているといってもよいと思う。プログラムを書くとき、それぞれの用語を用いて、会計システムであれば会計の用語や約束事を用いて、プログラムを記述できれば読みやすいプログラムを実現することができる。

そのように用意されたプログラミング言語ドメイン固有言語(domain specific language)というが、応用分野ごとにこのような言語を用意するのではたまらない。Haskellの良さは、応用分野に向けたデータ型を開発者が定義することで、ドメイン固有言語が容易に用意できることだ。先ほどらいの関手という概念を用いるならば、Haskellが用意している汎用的な世界を、新たに定義したデータ型が関手として働くことによって、応用分野に移してくれる。このため、開発者はHaskellのプログラミング手法を用いて、ドメイン固有言語が与えられているような感覚で、自然な姿で特定の分野に向けたプログラムを書くことができる。

Haskellでの関手を実装するためのプロトタイプを下図に示す。
f:id:bitterharvest:20170225113706p:plain
Haskellでは射は関数である。また、対象はデータ型と呼ばれる。Haskellのプログラムでは、関数の入力と出力の関係を型シグネチャで表す。入力と出力のそれぞれの値はあるデータ型に属している。しかし、そのデータ型はいくつかの可能性がある場合がある(例えば、加算であれば、整数であることもあるし、分数であることもあるし、小数であることもある)。そこで、可能性がいくつかある場合には変数と見なして小文字で表す(これを型変数という)。このため、圏では対象を大文字で表すのが通例となっているが、Haskellで記述するときは、小文字で表す。

圏論では関手によって写像される先は\(F(A),F(f)\)のように、\(F\)を用いて表していた。しかし、Haskellでは、対象と射の写像を別々に表す。対象はデータ型によって移され、射はクラス(この場合はファンクタを定めているクラス)をインスタンス化することで実現される。図に示したように、前者は

data Type_Constructor = Data_Constructor ..

で、後者は

instance Functor Type_Constructor where
  fmap f = ...

となる。

このテンプレートを用いて\(Maybe\)を実装したのが下図である。
f:id:bitterharvest:20170225113720p:plain

この上図で\(Maybe\)が型コンストラクタ、\(Nothing\)と\(Just\)がデータコンストラクタである。\(Maybe\)の横にある\(a\)を型引数と呼ぶが、\(a\)が属する型を\(Maybe\)というデータ型の世界に移したのが、ここでの記述である。


それでは、\(Maybe\)を定義することで、どのような応用分野が提供されたのであろうか。\(Maybe\)が利用される範囲が広すぎて具体的に述べることは難しいのだが、計算の世界で値が定まらなかったときにエラーが生じないような世界を提供したということになるであろうか。次回に話すが、余積の概念を実現したということだ。