読者です 読者をやめる 読者になる 読者になる

bitterharvest’s diary

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

境を明確にする対象

プログラマーのための圏論 (圏論とHaskell)

2.圏の定義

圏論の核となる圏の定義はとても単純である。抽象化は、細かいことを切り捨て、本質となている部分を取り出していく作業である。別の言い方をすると、数学の様々な分野での固有な部分を切り捨て、それらに共通する部分を抜きだすことである。圏論は抽象化の結果なので、その定義が単純なのは当たり前だとも思えるが、抽象化された後に得られた概念が大切なので、それらを少し詳しく説明する。

2.1 対象

数学で一番重要な概念は、等しいという概念だと思う。等しいという概念を最初に理解するのは、おそらく、三角形の合同だろう。二つの三角形を重ね合わせることができるなら、二つの三角形は等しいと学ぶ。しかし、これでは、数学的な厳密性を欠くので、一片とその両端の角がそれぞれ等しい時、二つの三角形は合同であるなどというように定義される。

三角形の合同は、それぞれの三角形の細かな違いを無視している。もしかすると、それぞれの三角形には、模様がついていて、それぞれで異なっているかもしれない。あるいは、三角形を作り出している素材が異なっているかもしれない。しかし、このような細部での違いを無視して、別の言葉を使うならば、抽象化をして、三角形の合同を定義している。

同じ性質を有するものを型という。型は自由に決めることができる。相互に合同である三角形を下図のように一つの型と決めてもよい。この型には無数の三角形が存在することになる(但し、全てが相互に合同である)。
f:id:bitterharvest:20161003095854p:plain

合同を学んだあと、相似な三角形を学ぶ。その後で、射影幾何学を学んだ人もいるだろう。ここでは、相似な四角形を定義できる。図のように一つの光源から光を四角形を当てると後ろに張った暗幕に影ができる。同一の影を作るような四角形は射影的合同と呼ばれるが、これに対しても型を作ることができる。
f:id:bitterharvest:20161004090228p:plain

もっと抽象化を行い、一つだけ穴が空いているものという型を作ることもできる(穴の定義が曖昧だが、例えば、これはホモトピー基本群が無限巡回群になる物体で、その次元を問わない)。
f:id:bitterharvest:20161003103336p:plain

型は、文学的に記述すればミウチとそうでない者との間に境目をつけることだ。少し前から読んでいた、永井路子著『美貌の女帝』は、蘇我氏の血筋を引くものを守り、新興勢力として伸びつつある藤原氏を排除することを政権の課題とする氷高皇女(ひたかひめみこ、後の元正天皇)の苦渋に満ちた人生を伝えている。実世界の中で、ミウチとそうでない者との間に境を設けようとすると、とても厳しい事態となるが、数学の世界では合理的に割り切って、境をつけることにしよう。

型は、圏論では対象(an object)という。対象はある性質を共有しているもの(数学的に等しいもの)である。対象には、いくつかの要素が含まれる(まったく含まれない場合もあるがこの話は後でする)。その数は、有限であるかもしれないし、無限であるかもしれない。先ほどの幾何学的な例に加えて、いくつかを紹介しよう。なお、対象を定義するときは、どのような性質を共有しているのかをよく考えながら定義することが大切である。曖昧に定義すると後でとんでもないしっぺ返しを食らう。

よく利用するのは、有限な単純な値の集まりであると思う。例を挙げてみよう。なお、ここでは、集合と言う言葉ではなく、集まりという言葉を使う(集合と言う用語を用いると、いわゆるラッセルのパラドックスに陥るので、ここではそれを避けるために集まりとする)。

1)単純な値の集まり(有限な個数)

・月={1月,2月,..,12月}
・曜日={日,月,火,水,木,金,土}
・苗字={青井,安藤,..,渡辺}

個数が有限でない場合、即ち無限の時は、数え上げられる場合とそうでない場合とに分けられる。まず数え上げられる場合を考えてみよう。

2)単純な値の集まり(無限だが数え上げできる個数)

自然数={1,2,3,..} (0を含める場合もある)
・整数={..,-1,0,1..}

数え上げられない場合は次のようである。

3)単純な値の集まり(数えきれないほどの無限な個数)

・半径1の円内の点
・実数
複素数

単純な値を組み合わせて、構造的な値を有するものもある。

4)構造的な値の集まり

・三角形
・正多角形
・合同な三角形
ホモトピー基本群が無限巡回群になる物体
・野菜
・車

Haskellでは対象はデータ型として表す。データ型は自由に定義することができるが、使用頻度の高いものはシステムであらかじめ用意している。それらは、

データ型説明
Int整数(有界\(-2^{63} \sim 2^{63}-1\))
Integer整数(無限)
Float単精度浮動小数点数
Double倍精度浮動小数点数
Bool真理値
Char文字(Unicode)

また、文字列Stringはデータ型ではないが、頻繁に用いるので、同じような感じで使えるようになっている。

また、新しいデータ型を用意することもできる。その時は、dataを用いる。例えば、曜日のデータ型Dayは次のようになる(なお、データ型の名前の、この場合Dayの、先頭は必ず大文字である。また、データ型の名前は型コンストラクタと呼ばれる)。

data Day = Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday deriving (Show, Read)

これをファイルDay.hsに書き込んでおこう。そして、このプログラムを動かしてみよう。Haskellは今年の5月21日に版の改正があり、最新のバージョンはGHC8.0.1である。Windows用には、WinGHCiを用いると便利である。動かした結果は次の通りである。
f:id:bitterharvest:20161003152753p:plain

ファイルを読み込んだ後で、データ型Dayが正しく処理されているかを見るために、検査(inspection)してみる。これにはコマンド:iを用いる。
正しく定義されているので、次は変数aの値を月曜日にしてみる。aの型(type)を調べてみる。これには:tを用いる。ちゃんとなっている。最後にaの値を求めるとMondayと出力される。

なお、データ型Dayを定義するときにderiving (Show,Read)とした。これについては射のところで詳しく説明するが、ここでは、読込み、かつ、出力できるようにしたと理解して欲しい。

図形を扱えるようにするために、ユークリッド座標上の点に関するデータ型Pointを用意しよう。

data Point = Point (Double, Double) deriving (Show, Read)

この定義をファイルPoint.hsに書き込み、これを読みだして使ってみよう。点\(p1=(2.4,4.9)\)と\(p2=(3,4)\)を作成してみよう。

Prelude> :load "Point.hs"
[1 of 1] Compiling Main             ( Point.hs, interpreted )
Ok, modules loaded: Main.
*Main> :i Point
data Point = Point (Double, Double) 	-- Defined at Point.hs:1:1
instance [safe] Read Point -- Defined at Point.hs:1:53
instance [safe] Show Point -- Defined at Point.hs:1:47
*Main> let p1 = Point (2.4, 4.9)
*Main> p1
Point (2.4,4.9)
*Main> :t p1
p1 :: Point
*Main> let p2 = Point (3, 4)
*Main> p2
Point (3.0,4.0)
*Main> :t p2
p2 :: Point


次はこれを用いて三角形のデータ型Triangleを定義してみよう。定義に当たっては重心と2頂点を入力することにしよう。定義は次のようになる。

data Triangle = Triangle Point Point Point deriving (Show, Read) -- the gravity center, two vertexes

この定義をファイルPoint.hsに追加して使ってみよう。
以下では、重心が\((0,0)\)、頂点が\((0,1), (\sqrt{3}/2,-1/2), (-\sqrt{3}/2,-1/2)\)の三角形\(t1\)を作ろう。作成に当たっては重心と最初の2頂点を用いる。
f:id:bitterharvest:20161004180048p:plain

Prelude> :load "Point.hs"
[1 of 1] Compiling Main             ( Point.hs, interpreted )
Ok, modules loaded: Main.
*Main> :i Triangle
data Triangle = Triangle Point Point Point
  	-- Defined at Point.hs:3:1
instance [safe] Read Triangle -- Defined at Point.hs:3:60
instance [safe] Show Triangle -- Defined at Point.hs:3:54
*Main> let t1 = Triangle (Point (0,0)) (Point (0,1)) (Point (sqrt 3, (-0.5)))
*Main> t1
Triangle (Point (0.0,0.0)) (Point (0.0,1.0)) (Point (1.7320508075688772,-0.5))
*Main> :t t1
t1 :: Triangle

さらに正多角形のデータ型RegularPolygonを定義してみよう。定義に当たっては辺の数と重心と頂点の一つを入力することにしよう。定義は次のようになる。

data RegularPolygon = RP Int Point Point deriving (Show, Read)  -- edges, the gravity center, a vertex point

dataの次にある単語はデータ型に与えられた名前で、前述したように、型コンストラクタと呼ぶ。これまで、Point, Triangleを定義してきた。ところで、等号の左側にも、これまでは型コンストラクタと同じ名前を用いてきた。しかし、等号の左側はデータコンストラクタと呼ばれ、型コンストラクタと同じ名前である必要はない。そこで、今回は、型コンストラクタにRegularPolygonを、データコンストラクタにPRという名前を用いることにした。

この定義をファイルPoint.hsに追加して使ってみよう。
以下では、重心が\((2,2)\)、頂点の一つが\((2,4)\)の五角形\(p1\)を作ろう。
f:id:bitterharvest:20161004192942p:plain

Prelude> :load "Point.hs"
[1 of 1] Compiling Main             ( Point.hs, interpreted )
Ok, modules loaded: Main.
*Main> :i RegularPolygon
data RegularPolygon = RP Int Point Point
  	-- Defined at Point.hs:5:1
instance [safe] Read RegularPolygon -- Defined at Point.hs:5:58
instance [safe] Show RegularPolygon -- Defined at Point.hs:5:52
*Main> let p1 = RP 5 (Point (2,2)) (Point (2,4)) 
*Main> p1
RP 5 (Point (2.0,2.0)) (Point (2.0,4.0))
*Main> :t p1
p1 :: RegularPolygon

図形につかれたので、車についてのデータ型を定義てみよう。メーカー名(company)と車種(style)と製造年(production)を属性に持たせることにしよう。これには、レコード構文を使うと便利である。レコードはいくつかのフィールドからなり、各フィールドは型を指定できるようになっている。そこで、companyとstyleを文字列で、productionを整数の型とし、次のようにCarを定義する。

data Car = Car { company :: String , style :: String , production :: Integer} deriving (Show, Read)

この定義をファイルCar.hsに追加して使ってみよう。
ここ10年使用している車\(c1\)を得てみよう。若者用に売り出されたのだが、現在は、残念ながら製造されていない。
f:id:bitterharvest:20161004202405j:plain

Prelude> :load "Car.hs"
[1 of 1] Compiling Main             ( Car.hs, interpreted )
Ok, modules loaded: Main.
*Main> :i Car
data Car
  = Car {company :: String, style :: String, production :: Integer}
  	-- Defined at Car.hs:1:1
instance [safe] Read Car -- Defined at Car.hs:1:95
instance [safe] Show Car -- Defined at Car.hs:1:89
*Main> let c1 = Car {company="Honda", style="Air Wave", production=2006}
*Main> c1
Car {company = "Honda", style = "Air Wave", production = 2006}
*Main> :t c1
c1 :: Car

動作を確認できたと思う。野菜についても同じように定義できるが、ここは、読者の方で考えてみよう。

これまで、型を作るためのいくつかの方法を述べたが、代数的な計算を利用してデータ型を作りたい場合には、代数的データ型がある。量子力学の世界をHaskellで表現しようとするとき、最も基本的な記述であるブラを代数的データ型を用いて定義した。再帰的な構造を持つ型を定義しようとするとき、代数的データ型は便利である。

最後にまとめです。圏論での対象は型である。型はある性質を表すものの集まりである。Haskellは対象をデータ型として表す。