bitterharvest’s diary

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

バターナッツかぼちゃの美しい姿を楽しめるローストに挑戦

お店でバターナッツかぼちゃを見つけた。昨年はハローウィンの頃だったので、今年は随分と店に出てくるのが早い。
f:id:bitterharvest:20170826184641j:plain
前回はこれを用いてポタージュを作った。形が変わっているので、料理を作っているときは、みんなの興味を引いたが、いざ、出来上がってみると、良く出回っているカボチャで作ったポタージュとの間で決定的な差がなく、違いを強調することができなかった。

そこで、今年は形の違いを強調した料理を試みることにした。もちろん、バターナッツかぼちゃの味も楽しめる料理だ。しかも、この料理は、時間はかかるが、手間がかからないという利点もある。バターと砂糖をのせてオーブンに入れるだけだ。

かぼちゃは皮が固く包丁の刃先が滑らないように気を遣うが、バターナッツかぼちゃは柔らかい。簡単に包丁が入るので助かる。まず、半分に切る。
f:id:bitterharvest:20170826185934j:plain
種子をスプーンで取り除く。
f:id:bitterharvest:20170826190042j:plain
それぞれにバター30gを種を取り除いたところに加える。そのあと、それぞれに砂糖大匙3杯をまぶす。
f:id:bitterharvest:20170826190119j:plain
オーブンを190度に予熱する。
オーブン皿にバターナッツかぼちゃをのせる。調理後の洗う手間を少なくするために、アルミホイルを下にひく。
f:id:bitterharvest:20170826190737j:plain

オーブンで1時間ほど焼く。途中香ばしいにおいがしてくる。焼き上がりはこのような感じ。
f:id:bitterharvest:20170826190934j:plain
種子が入っていたところに甘い汁がたまっている。これをこぼさないようにして、全体を皿に移して食す。
f:id:bitterharvest:20170826191036j:plain

バターナッツかぼちゃの食感は柿に似ている。ローストした後でもこの感触は残っていて楽しむことができる。ローストしたバターナッツかぼちゃはとても柔らかく、スプーンで簡単に身をすくうことができる。驚いたことに、表皮がとても薄い。紙の厚さくらいしかない。すべてが果肉と言ってもよいくらいなので、食べたあと何か得したような気分になった。

通常のプログラムをHaskellで記述する(4)

プログラムを書いているときに記憶力を疑うことが多い。プログラミングの経験が豊富なのだから、何も参考にすることなく、画面にプログラムを打ち込んでいけるのだろうと思われることも多いし、自分でもそう思っている。しかし、いざプログラムを打ち込む段になると、初歩的なことが記憶になく、手元にある本をめくって調べていることが多い。昨今の国会答弁ではないが「記憶にありません」と脳から言われる。これは、歳を取ったからという訳ではなく、若い時からそうなのだから、どうも脳の機能に関係しているようだ。

かつて、オーストラリアで自動車の免許を取った時に文化の差に唖然とさせられることがあった。日本ではどのような試験といえども、開始時間と終了時間は厳密に決められている。このようなあり方を疑ったこともない。ところが、オーストラリアでは、自動車免許の学科試験は、開始時間が決まっていない。オフィスの開いている時間であればいつ来てくれても結構となっている。そこで、便利なオーストラリアの免許を修得するために、ある日お昼を食べた後、試験会場に向かった。会場に入ると、何人かの人が試験を受けている。講義机に座っている監督官のところに行って問題用紙を受け取り、何時間の試験ですかと聞いた。監督官は、「何時間かけて下さっても結構ですよ。ただし、オフィスが閉まる時間までには提出してください。」と答えた。厳粛さが全く感じられない、開放的な試験に遭遇して、これまで抱いていた試験へのイメージが一変した。

オーストラリアの大学の期末試験もこのような感じである。このような試験を当たり前としているシドニー大学からの留学生が私の講義を受講した。講義中の受け答えからから優秀な学生だろうと想像したが、期末試験は失敗する可能性が高いのではないかと予想した。オーストラリアと異なり、日本の大学の期末試験は時間が限られている。このため、じっくり考えて答えを出すような学生には不利で、短い時間によさそうな答えを見つけられる学生に対して有利になっている。彼にとってはクイズ番組に出ているような感じだろう。案の定、彼の答案には白紙の部分が多かった。時間をかけることができれば、状況は変わっただろうにとこのとき感じた。

短時間で解答する脳と長い時間を必要とする脳について、長いこと疑問を持ち続けていた。しかし、進化心理学の本を読んでこの疑問に対する解を見つけることができた。二重過程理論(Dual Process Theory)と呼ばれるが、人間の脳は二つの異なるプロセスによって処理されている。一つはヒューリスティックな処理で、他の一つは論理的な処理である。ヒューリスティックな処理は、決められた時間の中でもっともらしい解を瞬時に出す。論理的な処理は、時間をかけて正しいと思われる解を理路整然と引き出す。

人間は、命に係わる状況に陥った時には、それを回避するための行動をすぐにとれるように仕組まれている。ヒューリスティックな処理はこのような状況に対応できるための手段だ。朝、近くの川に沿って散歩することを習慣としていて、1年に1回あるかないかだが、蛇に出くわすことがある。予想もしていないことなので、出会った瞬間に反射的に身を引いてしまう。これはヒューリスティックな処理だ。時間をかけて理性的に考えれば、蛇は臆病な動物なのでまず襲ってくることはない。身を引いて側溝に落ちるような行為をしなくてもよいと後で分かるのだが、これは後の祭りだ。

最近話題の将棋の藤井聡太4段は、時間が無くなってからの後半戦が特に強いので、ヒューリスティックな能力に長けているのだろう。たゆまぬ努力が大きく貢献しているようだが天性の部分も大きい。それに反して、長いことプログラミングをしている私は手元に本を置いての作業とは情けない気がする。彼ほどの能力があれば、反射的にプログラムを書けるのにと思うこともしばしばだ。しかし、決められた時間の中で解決しなければならない作業ではないし、プログラムを書くことで新しい発見もするので、論理的な処理を楽しむことにして、銀行のATMのプログラムを完成させることにしよう。

なお、二重過程理論については前回紹介した『モラル・トライブズ』に詳しく書かれている。また、網谷祐一著『理性の起源: 賢すぎる、愚かすぎる、それが人間だ』にも簡潔に紹介されている。
f:id:bitterharvest:20170826102352j:plain

11.4 利息のない預金のプログラム

Pythonで実現した銀行のATMのプログラムをHaskellで実現してみよう。取り敢えず、プログラミングの負荷を少なくするために、利息は付かないものとする。即ち、ただ、預金するだけのとても簡単なプログラムを実現することを考えよう。

状態を有するプログラムでは定石になっている状態を得る関数\(get\)と状態を設定する関数\(put\)を定義しておこう。

get = State (\s -> (s,s))
put s = State (\_ -> ((), s))

それぞれの関数はデータ型\(State\)の値である。\(State\)というコンテナで包まれているが、その中身は関数である。それぞれの関数は、\(get\)では状態をタプルで出力し、\(get\)では入力された状態を挿入して出力する。

それでは、銀行のATMのプログラムの作成に移ろう。\( (a,s)\)で\(a\)は預金額、\(s\)は現在高である。

ATMを使おうとしている段階では現在高だけなので\( (0,s)\)である。ATMに現金を投入した瞬間は\( (a,s)\)である。この預金は銀行の方で処理され現在高が\(s\)から\(s+a\)となる。このとき、預金額と現在高のタプルは\( (0,s+a)\)となる。

まず、預金口座を開設することにしよう。この関数をinitdepとしよう。ある程度の金額を預け入れることで始まる。開設後の状態、即ち現在高は預入額となる。また、関数の型シグネチャは\(s \rightarrow State s b\)であることに注意すると次のようになる。

initdep :: (Num s, Num a) => s -> State s a
initdep s =  put s >>= \_ -> return 0

上記のプログラムで開設時の預け入れによって生まれた現在高\(s\)を入力することにより、預金額と現在額の対を作る。この時、預け入れの処理は終了しているので、預金額は0であることに注意。
\(runState\)でこのプログラムを実行してみよう。

*Main> runState (initdep 500) 0
(0,500)

上記のプログラムで0を入力しているが、この値は\(s\)のデータ型と同じものであればどの値でもよい。例えば、100でもよい。

*Main> runState (initdep 500) 100
(0,500)

それではATMで預金するときの関数\(deposit\)を作成してみよう。ここでは、このプログラムは預け入れの処理を銀行側が終了したときの状態を出力することにしよう。すなわち、預け入れ前の現在高を\(s\)とし、預入額を\(d\)とすると、預け入れ処理が済んだ時の現在高が\(s+d\)に変わる。また、\(initdep\)と型シグネチャが同じであることを考慮して関数を作成すると次のようになる。

deposit :: (Num s, Num a) => s -> State s a
deposit d =  get >>= \s -> put (s + d) >>= \_ -> return 0

それでは、開設した後、100預け入れしたプログラムを、\(initdep\)と\(deposit\)とを\(>=>\)で合成して作成する。\(>=>\)の型シグネチャが\(( a -> State \ s \ b) -> ( b -> State \ s \ c) -> (a -> State \ s \ c)\)であることを考慮すると次のようになる。

*Main> f = (\a -> initdep a) >=> (\_ -> deposit 100)
*Main> runState (f 500) 0
(0,600)

500で開設したとして上記のプログラムを実行する。

*Main> runState (f 500) 0
(0,600)

現在高が600になっていることが確認できた。

同様にさらに50預金したとする。プログラムは

g = (\a -> initdep a) >=> (\_ -> deposit 100) >=> (\_ -> deposit 50)

実行してみよう。

*Main> g = (\a -> initdep a) >=> (\_ -> deposit 100) >=> (\_ -> deposit 50)
*Main> runState (g 500) 0
(0,650)

このように、預け入れ行為を行うたびに、\(deposit\)を前のプログラムに合成させればよいことが分かった。

利息が付く場合については次回の記事で説明しよう。そして、ここまでのプログラムのコードを記しておこう。

(>=>) :: (Monad m) => ( a -> m b) -> ( b -> m c) -> (a -> m c)
f >=> g = \a -> let mb = f a
                in mb >>= g

newtype State s a = State (s -> (a,s))

runState :: State s a -> s -> (a, s)
runState (State f) s = f s

get :: State s s
get = State (\s -> (s,s))

put :: s -> State s ()
put s = State (\_ -> ((), s))

instance Functor (State s) where
    fmap f (State g) = State (\s -> let (a, sa) = g s
                                    in ( f a, sa))

instance Applicative (State s) where
    pure a = State (\s -> (a, s))
    (<*>) mf ma = State (\s -> let (a, sa) = runState ma s
                                   (f, sb) = runState mf sa
                               in ( f a, sb))
instance Monad (State s) where
    ma >>= k = State (\s -> let (a, sa) = runState ma s 
                            in runState (k a) sa)
    return a = State (\s -> (a, s))

deposit :: (Num s, Num a) => s -> State s a
deposit d =  get >>= \s -> put (s + d) >>= \_ -> return 0

initdep :: (Num s, Num a) => s -> State s a
initdep s =  put s >>= \_ -> return 0

通常のプログラムをHaskellで記述する(3)

11.3 状態を表現するための準備をする

Haskellで状態を表すための準備をしよう。入力\(a\) を受けてその結果得られた状態\(s\)をタプル\( (a, s) \)であらわすことにしよう。

Hskellでプログラムを書くときは、しっかりと腰を据えて考えることが必要である。思い付きで記述したとしても、コンパイラで跳ねつけられてしまう。昨日は、車を運転しながらプログラムを考えていたので、青信号になったのに気が付くのが遅かったり、駐車場の入り口を通り過ぎたりと散々であった。事故を起こさなくてよかったが、やはり、書斎でじっくり考えて、納得してからプログラムの記述という肉体労働を始めるのがよい。

まずは、状態というデータ型を考えることにしよう。

入力が\(a\)から\(b\)に変わったとしよう。これを\(a \rightarrow b\)で表す。あるいは、\(f : a \rightarrow b\)と考えて、\(a\)から\(b=f(a)\)になったと考えてよい。そこで、入力と状態\(s\)のタプルはを考えることにしよう。これは入力が変化するとき、タプルの変化は\( (a, s) \rightarrow (b, s) \)となる。もちろん、左側の\(s\)と右側の\(s\)でも値は異なっていてよい。しかし、データ型は同じなので、同じ\(s\)で記述されていることに注意してほしい。

さて、\( (a, s) \rightarrow (b, s) \)はカリー化することで\(a \rightarrow (s \rightarrow (b,s))\)と書くことができる。このようにすると、\(s \rightarrow (b,s)\)の部分がだいぶ前の記事で出てきた\(Reader\)に似ていることに気がつかないだろうか。

\(Reader\)での可換図式に真似て、\(a \rightarrow (s \rightarrow (b,s))\)の可換図式を書くと次のようになる。
f:id:bitterharvest:20170825180411p:plain

この可換図式から\(a\)が射\(s \rightarrow (a,s)\)に関手によって移されていることが分かる。この時の関手を\(State \ s\)としよう。即ち、\(State \ s \ a = State(s \rightarrow (a,s))\)であり、\(State \ s \ b = State(s \rightarrow (b,s))\)である。そこで、これをデータ型にしてみよう。次のようになる。

State s a = State (s -> (a,s))

ここで、\(s\)は状態を表している。与えられた状態が入力とともに返されるのがこのデータ型の特徴である。これによって、状態を関数の間で持ち回ることが可能になる。

また、関数\(f\)は関手によって\(State \ s \ f = fmap \ f \)に移される。

そこで、関手\(State \ s\)をクラス\(Functor\)のインスタンスとして定義しよう。

instance Functor (State s) where
    fmap f (State g) = State (\s -> let (a, sa) = g s
                                    in ( f a, sa))

このプログラムでは、\( fmap \ f \ (State \ g)\)を求める。これは\(s\)から\( (b,s)\)への射である。上記のプログラムで、\( (b,s)\)は\( (f \ a,sa)\)である。\( (a,s)\)を、プログラムでは\( (a,sa)\)を、経由して求めている。

それでは、これをモナドとして定義しよう。定義する関数は\( (>>=)\)と\(return\)である。\( (>>=)\)の型シグネチャは\(m a -> (a -> m b) -> m b\)である。今回は、\(m = (State \ s)\)なので、置き換えると\(State \ s \ a -> (a -> State \ s \ b) -> State \ s \ b\)となる。即ち、\(State( s \rightarrow (a,s))\)を入力する。次に関数\( (a -> State \ s \ b)\)を実行する。この関数への入力は\(State( s \rightarrow (a,s))\)ではなく\(s \rightarrow (a,s)\)である。そして、この関数は\(State( s \rightarrow (b,s))\)を出力する。さらに、これが\( (>>=)\)の出力ともなる。

この関数を定義する前に、データ型Stateの値を得て、即ち\(State( s \rightarrow (a,s))\)を得て、これに\( s\)を得て、\( (a,s)\)を得る関数\(runState\)を定義しておこう。

runState :: State s a -> s -> (a, s)
runState (State f) s = f s

これを利用すると\( (>>=)\)は次のようになる。

    ma >>= k = State (\s -> let (a, sa) = runState ma s 
                            in runState (k a) sa)

このプログラムで\(k\)は上記の説明での関数\( (a -> State \ s \ b)\)である。これを利用するとモナドの定義は次のようになる。

instance Monad (State s) where
    ma >>= k = State (\s -> let (a, sa) = runState ma s 
                            in runState (k a) sa)
    return a = State (\s -> (a, s))

これで終わりなのだが、新しいHaskellではクラス階層の中で、\(Functor\)と\( M onad \)の間に、\(Applicative\)が定義されている。そこで、これを定義する。次のようになる。

instance Applicative (State s) where
    pure a = State (\s -> (a, s))
    (<*>) mf ma = State (\s -> let (a, sa) = runState ma s
                                   (f, sb) = runState mf sa
                               in ( f a, sb))

\(Applicative\)では\(pure\)と\( (<*)\)を定義する必要がある。このプログラムでは必要ないので、\(pure \ a = undefined\)そして\((<*>) \ mf \ ma = undefined\)でも構わない。

モナドを定義できたので、次回は銀行のATMのプログラムを完成させよう。

通常のプログラムをHaskellで記述する(2)

最近、進化心理学の本を立て続けに読んだが、その中で最も優れていると感じたのは、ジョシュア・グリーン(Joshua Green)の『モラル・トライブズ』であった。
f:id:bitterharvest:20170824084206j:plain

グリーンは高校生のとき、弁論部に属していたそうだ。対抗戦の弁論大会で、彼は「功利主義(utilitarianism)」を利用して相手を打ち負かすという戦略を思いつく。功利主義は、ジュレミ・ベンサム(Jeremy Bentham)と、ジョン・スチュアート・ミル(John Stuart Mill)らによって考え出された。これは、「最大多数の最大幸福」を追求する行為や行動が社会的に望ましいとする考え方である。

弁論大会で、グリーンは相手の主張が「最大多数の最大幸福」になっていないことをうまく利用して、連戦連勝を続けた。しかし、あるとき、トロッコの問題を出され(実際に出された問題はトロッコの問題を巧みに変形したものであった)、功利主義の弱点を突かれる。トロッコの問題は二つに分かれている。

一つ目は、「暴走したトロッコが坂道を転がり始める。途中、線路は二つに分岐している。一方の線路では5人が作業中である。他方の線路では1人が作業をしている。分岐点のところにあなたは立っている。そこには線路を切替えるためのスイッチがある。何もしないと5人の方にトロッコは向かう。もし、スイッチを押せば1人の方に向かう。あなたはスイッチを押しますか。」という問いである。この問いに対してはほとんどの人はスイッチを押す方を選択する。

二つ目は、「今度は、線路は一本で、その先には5人が仕事をしている。しかし、途中には橋が架かっていてそこには大きなリュックサックを背負った人がいる。もし、この人を橋から突き落とせば、トロッコは脱線して5人の命は助かる。あなたはこの人を突き落としますか。」という問いである。これに対しては、ほとんどの人は突き落とさないと答える。

前者も後者も肯定した答えは、5人の命を救い、1人の命を失うということで結果は同じである。功利主義の立場から言えばどちらも肯定しそうなものだがそうはならない。グリーンは功利主義では説明できないことに出会って、弁論部から離れることになる。そして、長いこと、功利主義からも離れる。

その後、グリーンは進化心理学の研究を始める。進化心理学は、モラルもチャールズ・ダーウィン(Charles Darwin)の進化論に沿って、自然選択されてきたというものだ。数家族を単位として狩猟採集生活を行ってきた新石器時代においては、血縁関係にある人々が生存できるように(血縁関係にあるものはDNAが類似しているので、このDNAを繁栄させるように働いた)、「私」のモラルを進化させ、農業革命が生じる新石器時代においては、同一出自集団の人々が生存できるように、「私たち」のモラルを進化させてきたとグリーンは言う。そして、「私たち」のモラルの進化は最近まで続く。

現代になって、国際化を迎えると、「私たち」のモラルと別の集団の「私たち」のモラル(「彼たち」のモラルと呼ぶこととする)がぶつかり合う。「私たち」のモラルも「彼たち」のモラルも、それぞれの中で最適と思われるように進化していて、多くの場合、その内部にいる人たちは正しいとさえ思っているので、「私たち」と「彼たち」のモラルが出会うと激しく衝突することも多い。

国際化時代にあっては、「私たち」のモラルと「彼たち」のモラルが共存できるようにモラルを進化させることが求められているというのが彼の主張である。その時に、ノーベル経済学賞を受賞したダニエル・カーネマン(Daniel Kahneman)の「幸福」という概念を用いて功利主義で考え、私たちと彼たちの「最大幸福」が得られるようにモラルを進化させるべきであると説いている。グリーンが高校生のときにかぶれた功利主義がまた彼の中で復活している点が面白い。

11.2 状態のあるプログラム

前置きが長くなり過ぎたので、今回は、状態のあるプログラムをPythonを用いて開発することにしよう。例として用いるプログラムは銀行のATMだ。といっても、預金しかできないいたって簡単なものだが、グローバル変数によって状態が表されている例を見ておこう。

現在高、利率をグローバル変数\(credit, interest\)で表すことにしよう。
預金の関数\(deposit\)は預金額\(d\)を入力し、そのあと前回の預け入れからの経過期間\(p\)を入力することとする。これにより、新しい現在高は、利息込みの現在高\(credit*(interest**p)\)に預金額\(d\)を加えたものとなる。

プログラムは次のようになる。

Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:57:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> credit=500
>>> interest=1.001
>>> def deposit():
  global credit
  dc=input('預金額=')
  d=float(dc)
  pc=input('前回からの経過期間=')
  p=float(pc)
  credit=credit*(interest**p)+d
  print('現在高='+str(credit))

>>> 

それでは、このプログラムを実行してみよう。

>>> deposit()
預金額=25
前回からの経過期間=5
現在高=527.5050050025002
>>> deposit()
預金額=25
前回からの経過期間=5
現在高=555.1478103552505
>>> 

\(deposit\)を起動するたびごとに利息と預金額によって新しい現在高が更新されていることが分かる。このプログラムでは状態をグローバル変数\(credit, interest\)で表している。そのうち、\(credit\)の方は値を変化させていくが、\(interest\)の方は固定である。
それでは、次回はこのプログラムをHaskellで実装してみよう。

通常のプログラムをHaskellで記述する(1)

11.通常のプログラムをHaskellで記述する

モナドの大きな目的は手続き型プログラミング言語で書かれたプログラムを副作用のない純関数型のプログラムで記述できるようにすることである。

通常のプログラミング言語は多くの場合手続きが他である。これには、C言語FortranJavaPythonなどが含まれる。手続き型プログラミング言語は部分関数、例外、状態などがあることが特徴であるが、これらの特徴が信頼性の低いプログラムを生み出す原因となっている。

そこで、信頼性の高いプログラムを提供するために、副作用のない純関数型のプログラムに書き直すことが望まれる。それは、命令型のプログラムを小さな部分に分けて、その部分を純関数型のプログラムで実装し、それらを接続することで実現できる。

ここではその例を示すために、Pythonで記述されたプログラムをHaskellで記述することを考えてみよう。

11.1 部分関数と例外の問題を解決する

次のプログラムは、円錐の体積を求めるものである。

Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:57:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> from math import pi
>>> rc=input('底面の半径は?')
底面の半径は?5.0
>>> r=float(rc)
>>> a = pi*r*r
>>> print('底面積は:'+str(a))
底面積は:78.53981633974483
>>> hc=input('円錐の高さは?')
円錐の高さは?2.0
>>> h=float(hc)
>>> v=h*a/3.0
>>> print('体積は:'+str(v))
体積は:52.35987755982989
>>> 

インターアクティブにプログラムを作成しているのでわかりにくいが、コードの部分だけを取り出すと以下のようになる。

from math import pi
rc=input('底面の半径は?')
r=float(rc)
a = pi*r*r
print('底面積は:'+str(a))
hc=input('円錐の高さは?')
h=float(hc)
v=h*a/3.0
print('体積は:'+str(v))

上記のプログラムは底辺の面積と円錐の体積を求める二つのプログラムから成り立っていることが分かる。それでは、底辺の面積\(area\)と円錐の体積\(volume\)を計算するプログラムをHaskellで定義してみよう。

Pythonのプログラムの中では省いたが、半径も高さも正の値でなければならない。半径も高さも実数ということにすると、\(area\)も\(volume\)も負の値が来たときは例外処理を行わなければならない。上記のPythonのプログラムではこの判定を行わなかったが、信頼性の高いプログラムを実現しようとするときは、負の値が入ってきたときの例外処理を行わなければならない。

プログラムはどのような実数も入力可能であるのに、受け付けることができる値は正数に限定されていることに上記のプログラムの問題がある。数学での関数は全関数であるのに対して、命令型のプログラムでは部分関数になっていることからこのような問題が生じている。

上記の問題を解決してくれるのがモナドである。Haskellでは\(Maybe,Either\)でこのような問題を解決できるようにしている。\(Maybe\)では受け付けられない入力に対しては\(Nothing\)を返す。受け付けられる入力に対しては、それを用いて計算しその結果に\(Just\)を付加して出力する。

Haskellでは\(area\)と\(volume\)の関数は次のようで定義できる。

area :: (Ord a, Floating a) => a -> Maybe a
area a = if a > 0 then return (pi * a * a) else Nothing

volume  :: (Ord a, Floating a) => a -> a -> Maybe a
volume a b = if a > 0 && b > 0 then return (a * b / 3.0) else Nothing

実行例は次のようになる。

*Main> area 5.0
Just 78.53981633974483
*Main> volume 2.0 78.5
Just 52.333333333333336

それでは、この二つの関数を接続することを考えよう。これは前の記事で説明したようにフィッシュ・オペレータを用いる。これは次のようになっている。

(>=>) :: (Monad m) => ( a -> m b) -> ( b -> m c) -> (a -> m c)
f >=> g = \a -> let mb = f a
                in mb >>= g

フィッシュ・オペレータを定義するためにはモナドを定義する必要があるが、幸いに、Maybeはモナドとして定義されているのでそのまま用いることにする。

それでは\(area\)と\(volume\)を接続しよう。

接続して、実行した例は次のようになる。

*Main> (area >=> volume 2.0) 5.0
Just 52.35987755982989
*Main> (area >=> volume 2.0) (-5.0)
Nothing
*Main> (area >=> volume (-2.0)) 5.0
Nothing
*Main> (area >=> volume (-2.0)) (-5.0)
Nothing

あるいは、次のように定義した後で実行してもよい。

*Main> g = \a b -> (area >=> volume a) b
*Main> g 2.0 5.0
Just 52.35987755982989
*Main> g 2.0 (-5.0)
Nothing
*Main> g (-2.0) 5.0
Nothing
*Main> g (-2.0) (-5.0)
Nothing

注:

\(Maybe\)をモナドとするためには\( (>>=)\)と\(return\)の対か\(join\)と\(return\)の対を定義する必要がある。\( (>>=)\)と\(return\)の対はHasekllで定義されているが、次のようになっている。

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
(Just a) >>= f = Just (f a)
_ >>= f = Nothing
return :: a -> Maybe a
a = Just a

上記の定義で

_ >>= f = Nothing

のところの_には実際には\(Nothing\)が来ることに注意。例外事象が生じたときに\(Nothing\)が出力されるので、\(f\)に先行するプログラムで例外が発生したときは、\(f\)のプログラムを実行せずに\(Nothing\)をパスすると解釈することができる。このため、前の方で起こった例外事象は後の方にあるプログラムを起動することなく伝えられることとなる。このため、Haskellでは例外事象を適切に処理していると言える。

\(join\)と\(return\)の対を用いたいのであれば次のようにする。

join :: Maybe (Maybe a) -> Maybe a
join Just (Just a) = Just a
join _ = Nothing
return :: a -> Maybe a
a = Just a

Haskellの難関モナドを突破するために掘り下げて学ぶ(4)

10.4 モナドを別の方法で定義する

前の記事でモナドを定義するときに\(>>=\)という関数を用いた。この関数の型シグネチャは次のようになっていた。

(>>=) :: \ m a -> (a -> m b) -> m b

ここで、\(m\)を関手と考えてみよう。関手\(F\)は下図のように定義されていた。
f:id:bitterharvest:20170806100853p:plain

上図で、関手\(F\)によって、対象\(A,B\)は対象\(F(A),F(B)\)に、また射\(f\)は射\(F(f)\)に写像される。

これを\(m\)を関手と考えてHaskellの記述で上記の図を書き直すと次の図のようになる。
f:id:bitterharvest:20170806101029p:plain

上図で、関手\(m\)によって、値\(a,b\)は値\(ma,mb\)に、また射\(f\)は射\(fmap \ f\)に写像される。少しわかりにくいのだが、\(a,b\)は型変数\(a,b\)がある型を取った時の値である。同様に、\(ma,mb\)は型変数\(m \ a,m \ b\)がある型を取った時の値である。また、Haskellでは\(F(f)\)は\(fmap \ f\)で表わす。

\( (>>=)\)のところで出てきた関数は\( (m \ a \rightarrow (a \rightarrow m \ b) \rightarrow m \ b)\)は、関手\(F\)による射\(f\)の写像\(F(f)\)を表していると考えよう。そうすると、\( m \ a\)が\(F (A)\)に、\(a \rightarrow m \ b\)が\(f\)に、\( m \ b\)が\(F (B)\)に対応している。そして、\(ma >>= f = fmap \ f \ ma\)となる。これから、\(b\)が\(mb\)であることが分かるので、上の図を書き直すと下図を得る。
f:id:bitterharvest:20170806102415p:plain

ところで、\( (>>=)\)の型シグネチャを見るとその出力は\(m \ b\)となっている。しかし、上の図での出力の型シグネチャは\(m \ (m \ b)\)である。このため、この矛盾を解決するためには、下図に示すように\(m b\)と\(m (m b)\)は同じでなければならない。
f:id:bitterharvest:20170806105932p:plain

\(m\)はコンピュータの世界から人間の世界へと視点となる世界を代えていた。丁度包み紙で包み込むような性質なのでこのようなものはコンテナと呼ばれる。\(m\)を二回かぶせた場合には人間の世界から人間の世界へと変えているので何も変わらない。即ち、コンテナを二回被せたものは一回被せたものと同じである。\(m \ (m \ b)=m \ b\)である。そこで、この性質を関数にし\(join\)と名付けよう。即ち、\(join :: m \ (m \ b) \rightarrow m \ b\)。このようにすると\(>>=\)は次のように定義できる。

(>>=) :: m a -> (a -> m b) -> m b
ma >>= f = join (fmap f ma)

モナドを\(>>=\)と\(return\)のペアではなく、\(join\)と\(return\)のペアで定義すると次のようになる。

class Functor m => Monad m where
  join :: m (m a) -> m a
  return :: a -> m a

前回と同じようにコンピュータの世界での表現に[ ]をつけたものを人間の世界での表現ということにすると[ ]をモナドとして定義できる。
これらをモジュールとして記述したのが下記のコードである。

module Program1 (Program1.Monad, (>=>)) where

(>=>) :: (Program1.Monad m) => ( a -> m b) -> ( b -> m c) -> (a -> m c)
f >=> g = \a -> let mb = f a
                in mb Program1.>>= g

(>>=) :: (Program1.Monad m) => m a -> (a -> m b) -> m b
ma >>= f = join (fmap f ma)

class Functor m => Monad m where
  join :: m (m a) -> m a
  return :: a -> m a

instance Program1.Monad [] where
  join [[a]] = [a]
  return a = [a] 

前回と同様にこれを利用するために下記のプログラムを用意する。

import Program1 as P

f' a = [a + 2]
g' a = [a * 3]
h' = f' >=> g'

前回と同じように実行すると次の結果を得る。

Prelude> :load "Main1.hs"
[1 of 2] Compiling Program1         ( Program1.hs, interpreted )
[2 of 2] Compiling Main             ( Main1.hs, interpreted )
Ok, modules loaded: Main, Program1.
*Main> f' 5
[7]
*Main> g' 7
[21]
*Main> h' 5
[21]
*Main> h' (-3)
[-3]

ところで、\(join\)は前々回の集合と冪集合を説明するときに出てきたものだ。冪集合から冪集合を作ったとしても元の冪集合と同じであるという話をしたが、これに相当するのが、\(join\)である。従って、今回のモナドの定義は集合と冪集合の関係を定義したものということができる。その時に用いた図を再掲する。
f:id:bitterharvest:20170806111003p:plain

モナドの理解が進んだところで、モナドの使い方を次回は学ぶ。

最後にモナドについてまとめておこう。

モナドとは、コンテナと呼ばれる性質を持つ関手である。関手は一方の世界(コンピュータの世界)の構造を他方の世界(人間の世界)に写すことができる。コンテナは一方の世界を他方の世界で包む性質である。

コンテナには二つの機能がある。一つの機能は一、方の世界のものを他方の世界で表現したものに写すものである。これは恒等射の性質を拡張したものである。二つ目の機能は関手を二回適応したものは関手を一回適応したものと同じであるというものだ。関手は一方の世界を他方の世界に写すだけでなく、他方の世界を他方の世界へ写すこともできる。後者は他方の世界で二重にくるむことになるが、これは一重でくるんだものと変わらないというのが二番目の機能である。

コンテナの二つの機能は\(return\)と\(join\)と呼ばれる二つの関数で実現される。

Haskellの難関モナドを突破するために掘り下げて学ぶ(3)

10.3 プログラムの合成

前々回の記事で紹介した二つのプログラムを決後するフィッシュ・オペレータをHaskellで実現することを考えよう。二つのプログラムを合成するとは、下図を実現することである。
f:id:bitterharvest:20170805091209p:plain

上図では二つのプログラム\(f,g\)がある。それぞれのプログラムは\(a,b\)を入力する。これらの値はコンピュータの世界で通用するものだ。そして、これらのプログラムは\(m \ b, m \ c\)を出力する。これらの値は人間の世界で通用するものだ。最初のプログラム\(f\)が吐き出す出力\(m \ b\)は、次のプログラム\(g\)への入力\(b\)となっているが、出力を直接入力として渡すわけにはいかない。人間の世界からコンピュータの世界への変換を行わないといけない。さて、これをHaskellで実現することを考える。

このように言われても戸惑うだろう。そこで、入出力が直接つながっている関数の合成\((.)\)をHaskellで実現し、そこからどのように実現したらよいかを学び取ることにしよう。

関数の合成は二つの関数を得て、それを合成し一つの関数として返すことである。Hasekllで実現すると次のようになる。最初の行が型シグネチャである。

(.) :: (b -> c) -> (a -> b) -> (a -> c)
g . f = \a -> let b = f a
              in g b

上記のプログラムで、\((b->c),(a->b)\)が入力される関数\(g,f\)である。\((a->c)\)が出力される関数で\(g \circ f\)である。

二番目の行が関数\((.)\)の定義である。\(a\)を入力し、それを関数\(f\)に適用し、その出力が\(b\)である。そして、この値を関数\(f\)に適用し、その出力が\(C\)である。

これをProgramというモジュールの一部とする。モジュールは次のようになる。

module Program ((Program..))) where

(.) :: (b -> c) -> (a -> b) -> (a -> c)
g . f = \a -> let b = f a
              in g b

これを用いてみよう。合成関数\((.)\)はHaskellでは最初から定義され与えられているので、元々あったものと区別するために、\((P..)\)で呼ぶようにし、次のようにメインモジュールを作成する。

import Program as P

f a = a + 2
g a = a * 3
h = g P.. f

上のプログラムで\(h\)が合成された関数である。これを用いてみよう。

Prelude> :load "Main.hs"
[1 of 2] Compiling Program          ( Program.hs, interpreted )
[2 of 2] Compiling Main             ( Main.hs, interpreted )
Ok, modules loaded: Main, Program.
*Main> h 5
21
*Main> h (-3)
-3

期待する結果は得られているようである。それでは、関数の合成のコードを見ながらプログラムの合成についてのコードを作成することにしよう。
変えなければならないところは、最後の行の\( g b\)である。ここは、出力\(b\)を得て、\(g\)に適応している。そこで同じようにしよう。最初のプログラムの出力は\(mb\)としよう。これは型\(m \ b\)に属す値という意味でこのようにした。また、これを受けて\(g\)に適応するのは\(mb >>= g\)としよう。

即ち、次のようにする。

(>=>) :: ( a -> m b) -> ( b -> m c) -> (a -> m c)
f >=> g = \a -> let mb = f a
                in mb >>= g

関数合成のコードは次のようになっていた。これと比較すると対応関係がよく分かる。

(.) :: (b -> c) -> (a -> b) -> (a -> c)
g . f = \a -> let b = f a
              in g b

前々回の記事で紹介したが、クライスリ圏は、\(>=>\)と\(return\)を定義すれば構成できる。\(>=>\)はいま出てきた\(>>=\)から得られるので、クライスリ圏は、\(>>=\)と\(return\)からも構成できる。そこで、この二つの関数を備えているクラスをモナドと呼ぶことにしよう。このように定めるとモナドは次のようになる。

class Monad m where
  (>>=) :: m a -> (a -> m b) -> m b
  return :: a -> m a

上記で、\(>>=\)は人間の世界からの入力\(m \ a\)、人間の世界への出力\(m \ b\)としている。しかし、ここで用いられる関数\(a \rightarrow m \ b\)は\(m \ a\)をコンピュータの世界の値\(a\)に変換されたものを用いて計算している。

今、人間の世界に移す時は[ ]で値を囲むことにする。そこで、[ ] をモナドとすることを考えよう。これは、次のように定義できる。

class Monad m where
instance Monad [] where
  [a] >>= g = g a
  return a = [a] 

そこで、今までのものをProgramというモジュールにまとめる。この時、Haskellで使われている関数との衝突を避けるために、これらには\(Program.\)をつける。

module Program (Program.Monad, (>=>)) where

(>=>) :: (Program.Monad m) => ( a -> m b) -> ( b -> m c) -> (a -> m c)
f >=> g = \a -> let mb = f a
                in mb Program.>>= g

class Monad m where
  (>>=) :: m a -> (a -> m b) -> m b
  return :: a -> m a

instance Program.Monad [] where
  [a] >>= g = g a
  return a = [a] 

それではこのプログラムを使ってみよう。利用するプログラムは以下のとおりである。

import Program as P
f' a = [a + 2]
g' a = [a * 3]
h' = f' >=> g' 

先ほどと変わらないが、出力がカッコで囲まれていることに注意してほしい。

Prelude> :load "Main.hs"
[1 of 2] Compiling Program          ( Program.hs, interpreted )
[2 of 2] Compiling Main             ( Main.hs, interpreted )
Ok, modules loaded: Main, Program.
*Main> h' 5
[21]
*Main> h' (-3)
[-3]

これで、プログラムの合成ができるようになった。次回は、集合と冪集合での関係を用いてモナドを定義する。

Haskellの難関モナドを突破するために掘り下げて学ぶ(2)

10.2 集合と冪集合

数学は全く異なるものの間の中に共通する性質を見出し新しい概念を引き出すことを得意とする学問だ。ここでも、後でまさかと思うことを説明するのだが、その準備のために集合について少し説明しておこう。

集合は要素の集まりと最初は教わる。例えば、集合\(S\)が要素\(a,b,c\)の集まりである時は、\(S=\{a,b,c\}\)と記す。

次に集合の部分集合を学ぶ。これは、ある集合を構成する要素の一部分を集めて作った集合である。例えば、要素\(a,b\)で構成される集合\(S'=\{a,b\}\)は\(S\)の部分集合である。これは、\(S' \subset S\)と記す。部分集合には要素が皆無である物もすべて含んでいるものも含まれる。

そこで、部分集合を集めたものもまた集合である。例えば、\(S''=\{a,c\}\)としたとき\(T=\{S',S''\}=\{\{a,b\},\{a,c\}\}\)は集合である。そこで、部分集合で構成される集合の全てを集めたものを考えることにしよう。これを冪集合と呼ぶことにしよう。先に説明した集合\(S=\{a,b,c\}\)の冪集合\(P(S)\)は
\begin{equation}
P(S)=\{\{\},\{a\},\{b\},\{c\},\{a,b\},\{a,c\},\{b,c\},\{a,b,c\}\}
\end{equation}
となる。

冪集合も集合なのでその冪集合を取ることができる。そこで、\(P(S)\)の冪集合\(Q(P(S))\)は、

それぞれの要素を部分集合にしたもの:\(\{\{\}\},\{\{a\}\},\{\{b\}\},\{\{c\}\},\{\{a,b\}\},\{\{a,c\}\},\{\{b,c\}\}\},\{\{a,b,c\}\}\)

\(\{\}\)と残りの要素のそれぞれを組み合わせて部分集合にしたもの:\(\{\{\},\{a\}\},\{\{\},\{b\}\},\{\{\},\{c\}\},\{\{\},\{a,b\}\},\{\{\},\{a,c\}\},\{\{\},\{b,c\}\},\{\{\},\{a,b,c\}\}\)

\(\{a\}\)と残りの要素のそれぞれを組み合わせて部分集合にしたもの:\(\{\{a\},\{b\}\},\{\{a\},\{c\}\},\{\{a\},\{a,b\}\},\{\{a\},\{a,c\}\},\{\{a\},\{b,c\}\},\{\{a\},\{a,b,c\}\}\)

\(\{b\}\)と残りの要素のそれぞれを組み合わせて部分集合にしたもの:\(\{\{b\},\{c\}\},\{\{b\},\{a,b\}\},\{\{b\},\{a,c\}\},\{\{b\},\{b,c\}\},\{\{b\},\{a,b,c\}\}\)

\(\{c\}\)と残りの要素のそれぞれを組み合わせて部分集合にしたもの:\(\{\{c\},\{a,b\}\},\{\{c\},\{a,c\}\},\{\{c\},\{b,c\}\},\{\{c\},\{a,b,c\}\}\)

\(\{a,b\}\)と残りの要素のそれぞれを組み合わせて部分集合にしたもの:\(\{\{a,b\},\{a,c\}\},\{\{a,b\},\{b,c\}\},\{\{a,b\},\{a,b,c\}\}\)

\(\{a,c\}\)と残りの要素のそれぞれを組み合わせて部分集合にしたもの:\(\{\{a,c\},\{b,c\}\},\{\{a,c\},\{a,b,c\}\}\)

\(\{b,c\}\)と残りの要素のそれぞれを組み合わせて部分集合にしたもの:\(\{\{b,c\},\{a,b,c\}\}\)

これらを集めたものである。この関係を図に表したのが下図である。
f:id:bitterharvest:20170804051951p:plain

しかし、上記の記述では、それぞれの部分集合は部分集合を集めたものとなっている。例えば最後の部分集合\(\{\{b,c\},\{a,b,c\}\}\)である。しかし、これは実は\(\{a,b,c\}\)と同じである。即ち、冪集合から作られた冪集合を構成する部分集合は、内側にある\(\{\}\)をはずし同じ要素を一つにまとめたもの(和集合)と同じである。また、要素が一つだけの部分集合のカッコも外すと、次のようになる。
\begin{equation}
Q(P(S))=\{\{\},a,b,c,\{a,b\},\{a,c\},\{b,c\},\{a,b,c\}\}
\end{equation}

同じように\(P(S)\)の方も要素が一つだけの部分集合のカッコを外すと、次のようになる。
\begin{equation}
P(S)=\{\{\},a,b,c,\{a,b\},\{a,c\},\{b,c\},\{a,b,c\}\}
\end{equation}

これより、
\begin{equation}
Q(P(S))=P(S)
\end{equation}
であることが分かる。

そこでこれを図で表すと次のようになる。
f:id:bitterharvest:20170804075436p:plain

上図で\(P(S)\)は\(S\)を含んでいるので、集合を冪集合の部分集合にして図を書き直すと次のようになる。
f:id:bitterharvest:20170804154859p:plain

さて、\(S\)と\(P(S)\)をHaskellでの型と思って、それぞれを型変数\(a\)と\(m \ a\)で書き換え、冪集合をモナドで書き換えると下図を得る。
f:id:bitterharvest:20170804054649p:plain
ここで、\(m \ (m \ a)=m \ a\)である。

上記では、集合と冪集合を型変数で書き換えることで抽象化をおこなった。ここで得られたものがモナドと呼ばれるものである。モナドと言われたら、集合と冪集合の関係を思い出すか、あるいは、前回の記事で述べたようにCやJavaのような命令型の言語で書かれたプログラムを思い出すと分かりやすいと思う。

Haskellの難関モナドを突破するために掘り下げて学ぶ(1)

10.モナドを掘り下げよう

圏論についての説明は前回の記事で一応終了した。しかし、ある事項についてはもう少し詳しく知りたいということがあるだろう。そのうちの一つはモナドだと思う。モナドは慣れてくるととても便利な道具なのだが、そこまでに行き着くのにはそれなりの努力が必要だ。そこで、ここでは、モナドについてもう少し踏み込んで説明しよう。

10.1 圏論とプログラム

まず、関数から説明することにしよう。Milewskiが面白いたとえを用いて説明しているので、それを利用させてもらおう。関数とは入力と出力があり、入力に対してある操作を加え、その結果を出力する。

ここからはMilewskiの説明をベースにして作った話だ。プログラマーが南の島へ旅行に行ったとしよう。島についた彼は、漁師たちから彼らを背の高い順に整列させて欲しいとせがまれた。彼はソートのアルゴリズムを利用して漁師を背丈で昇順になるように一列に並べた。上の例では、集まってきた漁師たちが入力、背丈の順に一列に並んだ漁師が出力、プログラマーが関数である。この関数を\(f\)としておこう。

同じように、漁師たちが収穫した魚の量によって彼らを一列に並べたとする。この時の入力はやはり集まった漁師たち、出力は収穫の量に応じて昇順に並んだ漁師、関数はプログラマーである。また、この関数を\(g\)としておこう。

圏論で重要な考え方の一つは、関数の合成である。複雑な作業は、いくつかの関数を繋ぎ合わせたもので構成される。例えば、漁師たちを収穫の量に応じて整列させるのだが、収穫量が同じ場合には背の高さで並んでいるようにしたいと考えたとしよう。この場合、上記の二つを用いて、関数\(f\)を適応した後に、その出力を関数\(g\)の入力とすればよいことに簡単に気が付くであろう(\(g\)の後に\(f\)を適応すると予想した結果にはならないことに注意)。二つの関数を連続して用いることを関数の合成と言い、この場合には\(g \circ f\)と書く。

このように、複雑な問題はより簡単な問題の合成へと分解することを繰り返すことで、難しい問題を解くことができるようになる。

ここで、Milewskiが突飛な例を出した。関数を動物とし、入力を食物、出力を排泄物としよう。さて、動物同士を繋げたらといった瞬間、聴衆の一人が笑いだし、止まらなくなった。

複雑な問題の解決はプログラムでも同じである。複雑なプログラムを設計するときは、これをより単純なサブプログラムの合成へと分解することで、上記の場合と同じように解決することができる。関数の合成は\( \circ \)で表したがプログラムの合成を\(>=>\)で表すことにしよう。プログラムも入力を出力に変えてくれる。ただ、異なるのは入力と出力の住んでいる世界が異なる点だ。例えば、整数の二乗を行うプログラムを考えよう。入力はコンピュータの世界で取り扱っている整数だ。これに対して出力は人間の世界が取り扱っている整数だ。整数という概念に対してはほぼ同じだが、その表し方は根本的に異なっている。そこで、コンピュータの世界での値\(a\)を人間の世界では値\(m \ a\)と表すことにしよう。なお、ここで\(m\)はモナドというコンテナで包んだというように解釈することにしよう。ただしモナドもコンテナもこれからの説明になるので、この段階では\(m\)は人間の世界で包んだという解釈にしておこう。

下図にプログラム\(f\)と入力\(a\)出力\(m \ b\)の関係を示した。
f:id:bitterharvest:20170805090020p:plain

また、プログラムに対しても少し制限を設けることにする。ここでいうプログラムは、CやJavaなどの命令型言語で書かれたものとする。しかし、このプログラムはいわゆる内部状態を持たないものとする。通常、内部状態をグローバル変数で持たせることが多いがそのようなものが定義されていないものとして考えることにしよう。また、プログラムを構成するサブルーチンはその出力は内部入力だけによって決まるものと考える。即ち、純粋な関数になっているとする。このようにすると、コンピュータにアクセスした人のログを取るようなプログラムは作れないのではと直感的に想像されるかもしれないが、そのようなことはない。これは再帰的な呼び出しを用いることで可能となる。詳しく知りたい人はMilewskiの記事を参照するとよい。

そこで、二つのプログラムを繋げるときは次のように考える。二つのプログラムを\(f,g\)とし、その入出力は次のようになっているとする。

f :: a -> m b
g :: b -> m c

二つのプログラムが合成した様子を下図に示す。
f:id:bitterharvest:20170805091209p:plain

プログラム\(f\)を実行した後でその結果に対して\(g\)を実行したとする。プログラムの合成\(>=>\)をHaskellでの型シグネチャは次のように表すことができる。

(>=>) :: (a -> m b) -> (b -> m c) -> m c 

圏論では関数の合成のほかに恒等射が重要な役割を果たす。そこで、恒等射は与えられた値をそのまま返す関数である。プログラムの世界ではコンピュータの世界の値を人間の世界での値にして返す関数になるので、この関数を\(return\)としよう。Haskellでの型シグネチャは次のようになる。

return :: a -> m a 

このように定めると、関数の合成と恒等射は圏論とプログラムでは次のように対応している。

機能 圏論 プログラム
関数合成 \(g\circ f\) \(f>=>g\)
恒等射 \(id\) \(return\)

さて、これまでプログラムということで説明してきたが、関数の合成を\(f>=>g\)で定義し、恒等射を\(return\)で定義したとき、単位律と結合律が成り立っていれば、これは圏となる。このような圏をクライスリ圏(Kleisli category)と呼ぶ。そして、\(>=>\)はフィッシュ・オペレータ(fish operator)と呼べれる。

フィッシュ・オペレータを自分の力で定義できれば、モナドについての説明は終わりである。次回は確認したい方のために、フィッシュ・オペレータを定義する。

恋人までの距離(Before Sunrise)-再会を期しての別れの場面

ジェシーセリーヌはウィーンの街へと繰り出す。かつてはハプスブルグ家がこの地に居城を開きヨーロッパの中心となっていた。さぞかし、有名な遺跡を巡るのかと思うとその期待は裏切られる。そう、二人にとっては観光旅行ではなく短いが楽しい時間そして愛をはぐくむときなのだから、会話だけがずっと続き景色は流れていく。
f:id:bitterharvest:20170802095741j:plain
プラーター公園で思い出に残る一晩を過ごした二人にも別れの時が来る。再びウィーン中央駅。エリーヌがパリへ向かう列車に乗り込む時二人は次の再会を約束する。
f:id:bitterharvest:20170802095022j:plain
いよいよ、別れの時が近づいてくる。二度と会わないと決めていたジェシーだが、それに耐えられなくなって切り出す。
“We're talking about not seeing each other again? I don't want that.”
「二度と会わないといっているけど、そんなの嫌だ」
セリーヌもそれに応じて、
“I don't want neither.”
「私もよ。」と言う。ジェシーは反復して、
“You don't neither?”
「あなたも。」と確認する。
“I waited for you to say it.”
「そう言ってくれるのを待っていたの。」とセリーヌが付け加える。
“Why didn't you say something?”
「なぜ言ってくれなかったの。」とジェシーがなじる。
“I was afraid you didn't wanna see me.”
「もしかして私に会いたくないではと考えてしまって聞けなかったの。」と微妙な心の動きを伝える。

そして、ジェシーは再会を約束するために切り出す。
“What do you wanna do?”
「それではどうしたい。」とジェシーは尋ねる。
“Maybe we should meet here in five years or something.”
「5年後ぐらいにここで会うのはどうかしら。」とセリーヌが提案する。ジェシーは即座に、
“Five years? That's a long time.”
「5年後。それは長すぎるよ。」と答える。
“it's awful. It's like a sociological experiment. How about one year?”
「そうよね。社会科学の実験みたいだわ。それでは1年後はどう。」と再提案する。
“One year. How about six months?”
「1年後。」ともう少し何とかならないかなあという姿勢をジェシーが示し、「6か月後では。」と新たな提案をする。
“Six months? It's gonna be freezing.”
「6か月後。凍てつくような寒さじゃない。」とセリーヌが躊躇する。
“Who cares? We come here, we go somewhere else.”
「誰がそんなこと気にするの。ここに来て、どこかに行けばいい。」
ジェシーが答えて次に会うことを約束する。さらに細かい時間や場所の取り決めをした後、会うときまではお互いに連絡を取り合わないことを約束して別れる。
別れの跡の画面が続く。その中の一つ。セリーヌが去った後の駅の様子を構外から見ているところ。
f:id:bitterharvest:20170802095241j:plain

さて、二人は6か月後に会えたであろうか。この続きは次作の『Before Sunset』で紹介されている。

恋人までの距離(Before Sunrise)-出会いの場面

専門分野の話は英語で話されても100%理解できる。しかし、映画となるとどうも心もとない。日常使うような言い回しを受験英語で学ぶことがほとんどなかったので戸惑いを感じるためだろう。

先日、カリフォルニアに住んでいるEdとGayeから、来年の秋にハワイのコンドミニアムで一緒に休暇を取らないかという誘いがきた。最近、英語を聞く機会がないので、力が落ちている可能性が高い。念のためにそれまでに耳を馴らしておいた方がよさそうだ。そこで、集中的に映画を観て準備することにした。ただ、観ただけでは面白くないので、その中で出てきた面白い表現を紹介することにしよう。

映画の説明に入る前に受験英語で失敗した例を最初に取り上げておこう。アメリカに留学し始めたころ、あるアメリカ人の家に泊まる機会があった。家に到着すると、両親はそのまま買い物に出かけてしまい、小学校低学年の子どもと彼の友達と一緒にいきなり留守番をさせられた。トイレの場所が分からなかったので子供たちに尋ねた。
“Where is the lavatory?”
そうしたら、予想もしないことが起こった。子供たちはいったん沈黙し、次の瞬間笑い転げながら部屋中を走り始めた。何とも古風な英語を東洋人から聞いてびっくりしたと同時におかしくなったのだろう。アメリカではbathroomが一般的だ(アメリカの家は浴室とトイレは一緒の部屋)。しかし受験英語ではこのようなことは教えてくれなかった。教科書として採用されるのは時代的には少し古い英語の小説(例えば、学校教育で英語を学ぶ最後となる大学2年の時はハーマン・メルヴィルの『白鯨』)がその頃はふつうであった。そこに出てくる単語を使ったので、「お主、厠はいずこにござ候。」ぐらいに聞いたのであろう。子供たちがびっくり仰天するのは当然のことであった。

最初に取り上げるのは、1995年作の『Before Sunrise (恋人までの距離)』である。偶然に車中で出会った男女が次の日の朝までのウィーンをぶらつきながら結びつき(connection)を強めていくという映画だ。

ジェシー(イーサン・ホークが演じる)はユーレイルパスを利用してブタペストからウィーンに向かっている。映画は流れていく線路を映し出した後ヨーロッパの美しい車窓を描き出す場面で始まる。
f:id:bitterharvest:20170731095650j:plain

ヨーロッパの6月は美しい。June Bride(6月に結婚式を挙げる花嫁)という言葉もあるぐらいで、人生の中で最も思い出に残るイベントをするべき時期と長いこと考えられてきた。この映画でも二人は6月16日に会っている。なだらかにうねっている薄緑色の丘陵地、それを囲むような深い緑の森、点在している白い壁の家々、そして青い空が織りなす風景がとてもきれいだ。

ソルボンヌ大学の女学生のセリーヌ(ジュリー・デルピー)は運悪く夫婦喧嘩をしているドイツ人夫婦とは通路を挟んで隣の席に座ってしまう。
f:id:bitterharvest:20170731112007j:plain
あまりの騒々しさに席を代え、ジェシーとは通路を挟んだ隣の席に移る。タイミングを計ってジェシーセリーヌに話しかける。とっつきに選んだのはもちろん夫婦喧嘩。二人とも何を話していたかは理解できなかったので、セリーヌが一般論に切り替える。
“Have you heard that as couples get older they lose their ability to hear each other?”
「夫婦は年を取ると相手が言っていることを聞く能力が失われるというのを聞いたことがある。」
セリーヌがもう少し詳しく説明した後で、ジェシーが次のようにまとめる。
“Nature's way of allowing couples to grow old together without killing each other, I guess.”
「夫婦がともに年を取ることができるように、そして連れ合いを殺さないで済むようにするための神からの恩恵だね。」
nature's wayの訳し方に工夫が必要だが、ここでは意訳した。

その後二人は、食堂車に移りコーヒーを飲みながら自己紹介をする。
f:id:bitterharvest:20170731112358j:plain
そうこうするうちに、ジェシーが下りるウィーンに到着する。彼は次の朝の飛行機でアメリカに帰ることになっているが、それまでの時間をセリーヌとともに過ごしたいと考えて、巧みにセリーヌを口説きにかかる。

列車から降りかけたジェシーは思いとどまり、セリーヌのところに再びやってくる。ヨーロッパの列車は大きな駅では長い時間停車している。日本でこんなことをしていると次の駅に連れていかれてしまうけれども、ヨーロッパでは十分に停車中の時間を楽しめる。また、ウィーンのような大きな駅は引き込み線になっている。丁度、日本の終着駅と同じで線路が行き止まりになっている。
f:id:bitterharvest:20170731112805j:plain
セリーヌの席にたどり着いたジェシーが次のように言う。
“I have an admittedly insane thought. If I don’t ask you this, it’ll haunt me the rest of my life.”
「自分でも非常識だと思うけど、このことを尋ねなければ、これからずっとこのことが頭から離れないと思うので、思い切って聞くけどいい。」
セリーヌが聞く。
“What?”
ジェシーが思いきって言う。
“I want to keep talking to you.”
「あなたと話し続けたい。」
“I have no idea what your situation is, but I feel like we have some kind of...connection.”
「あなたがどう思っているかは分からないけど、ある種の結びつき(connection)が僕たちにはあるように感じる。」
セリーヌも同じだという。「それでは、ウィーンで一緒に降りよう。僕は明日朝の飛行機に乗ることになっている。ただ、お金がなく、ホテルには泊まれないので、ウィーンの街を一緒に散策しよう。」と提案する。そして、極めつけの口説きに入る。
“Think of it like this.”
「このように考えてごらん。」
“Jump ahead, ten, twenty years, okay? And you're married.”
「10年後あるいは20年後の世界に飛び込んでみよう。そして、あなたは結婚している。」
“Only your marriage doesn't have that same energy that it used to have, you know. You start to blame your husband.”
「あなたの結婚には以前のような情熱はすでになく、ご主人を非難し始めている。」
“You start to think about all those guys you've met in your life, and what might have happened if you'd picked up with one of them?”
「これまでに会ったすべての男の人たちについて考え始めている。そのうちの一人をもし選んでいればどんなことが起こったのだろうかと考えている。」
“I'm one of those guys. That's me.”
「僕はそのうちの一人だ。そう僕だ。」
“So think of this as time travel from then to now to find out what you're missing out on.”
「これをタイムトラベルとして考えよう。未来から今への。あなたが若いころに逃したものを発見するための。」
“What this could be is a gigantic favor to both you and your future husband, to find out that you're not missing anything.”
「このことは素晴らしい贈り物だ。あなたとあなたの未来のご主人にとって。そして、あなたは何も逃していないことを発見する。」
“I'm just as big a loser as he is. Unmotivated. Boring.”
「僕は彼と同じでドジな男だ。やる気もないし、退屈な人間だ。」
ここは二人ともつまらない男だといっている。本来はご主人の方が優れていると言って良かったねというところなのだろうがそうではない。未来のご主人になれなかったジェシーが優れていたわけではないといっている。同程度にドジな男だといっている。どちらを選んでもよかったのだから、未来のご主人を選んだことに誤りはなかったと伝えたいのであろう。ご主人の方が優れていると言わなかったことで、ジェシーのプライドを示したかったのだろう。何となく引っかかる言回しなのだが、字幕の方は最初の文章は訳していない。単につまらない男だと伝えている。
“You made the right choice. You’re happy.”
「あなたは正しい選択をした。そして幸せだ。」

ジェシーの口説きは成功し、二人は列車を降りウィーンの街へと繰り出す。

映画の冒頭はドイツ語だった。列車がオーストリアを走行しているのでドイツ語で会話していることに違和感はないけれどもその他に理由はないのだろうか。国際列車なのでイタリア語でもよさそうだしフランス語でもよさそうだ。でもここは夫婦喧嘩の場面である。ヨーロッパで使われている主要な言語の中で、言い争っている場面に一番合いそうな言語は何と尋ねられればドイツ語と答えたい。力強く聞こえる言語で相手を説き伏せるには適しているように思える。

ウィーンはオーストリアの首都だ。標準語はもちろんドイツ語。歴史的な遺産がたくさん残されていて、旅行者から愛されている街だが、二人のconnectionはどのように展開していくのであろうか。それは次回の楽しみにということで今回はここまで。

若鶏のバスク風

鶏を丸ごと買ってきて作る料理を紹介しよう。頻繁に訪れる大型スーパーは周辺に外国人が多く住んでいるためかサイズの大きな肉が豊富に取り揃えられている。ステーキ用に300gサイズの豪州産牛肉が大量に並べられている。豚のステーキ肉も通常のものよりは2倍の厚みがある。そのような中で前から目をつけていたのが丸ごとの若鶏。一度購入しておいしい料理をと考えていた。

フランス料理の本をめくっていると、「若鶏のバスク風」というレシピに目が行った。バスクピレネー山脈の両麓に住んでいる人々を指す。西側がスペインに住むバスク人、東側がフランスに住むバスク人だ。彼らはバスク語を話す。今日のヨーロッパ言語とは親戚関係にない孤立した言語だ。彼らのY染色体はハプログループR1bが9割を占める。このハプログループの人々は新石器時代アナトリア(西アジア)からヨーロッパにわたり農耕を伝えたと考えられている(日本列島での弥生人と同じような役割を果たした)。かつてスペインのバスク地方の中心地ビルバオを訪問したことがある。とても良い印象を受けたこともありこの地方の料理を試みることにした。

今日の食材たちに登場してもらおう。
f:id:bitterharvest:20170718091329j:plain
主役はもちろん若鶏。脇役はトマト。味が淡白な若鶏をトマト味でスープに浸していただこうというものだ。トマト味に深みを持たせるのが、玉ねぎ(1個)、パプリカ(赤と黄のそれをそれぞれ半分)、ピーマン(3個)だ。パプリカは料理に彩を与える効果もある。肉の臭みはニンニクとブーケガルニだ。ブーケガルニは庭にあるハーブから作った。ローズマリー、セージ2種類、ラベンダー、レモンバームをただ束ねただけだ。

最初に取り掛かるのは若鶏をさばくことだ(丸ごと一羽で税抜き599円だった)。
f:id:bitterharvest:20170718092533j:plain
包丁でさばいてもよいのだが切れ味よく研いでないと肉が逃げるのでけがをする可能性が高い。簡単なのは調理用のはさみだ。レッグと手羽の部分はその付近の皮と腱を切り関節のところで折ると簡単に分離する。胸肉は骨に沿って肉を切り取ればよい、胸の骨はじょきじょきと簡単に切れるので適当な大きさに切ってだしとして用いればよい。切った後に塩・胡椒をする。
f:id:bitterharvest:20170718093405j:plain

玉ねぎ、パプリカ、ピーマンは1cm幅ぐらいに切る。
f:id:bitterharvest:20170718093751j:plain

大きなフライパンにオリーブオイル30ccを加えニンニクの細切りを加えてニンニクの焦げたにおいがするまで炒める。
f:id:bitterharvest:20170718094342j:plain
鶏肉を皮の方を下にしてフライパンに入れて焼き目が付いたころ裏返しにする。肉の側にも焼き目が付いたころ皿に移す。
f:id:bitterharvest:20170718094400j:plain

フライパンからニンニクを取り出し、オリーブオイルを20cc加えて玉ねぎ、パプリカ、ピーマンを加え、野菜類がしなやかになるまで炒める。そして、塩・胡椒をする。
f:id:bitterharvest:20170718094640j:plain

ホールトマト(1缶400g)をフライパンに加える。
f:id:bitterharvest:20170718095009j:plain

鶏肉をフライパンに戻し、ブールガリア、グリーンオリーブ、白ワイン(70cc)もフライパンに加える。
f:id:bitterharvest:20170718095153j:plain

弱火で15分間煮る。最後に塩・胡椒で味を調える。
f:id:bitterharvest:20170718095240j:plain

今日の主食とごはんとワインだ。
f:id:bitterharvest:20170718095359j:plain

孫がサッカーの試合を近くでしていたので夕食に加わった。お腹がすていたこともあるだろうが美味しい美味しいといって食べてくれた。

恩田川を散歩する

先日wowowで『4月の君の嘘』という映画を観た。高校生のラブストーリーだ。広瀬すずが扮するヴァイオリニストのかおりは余命いくばくもない。山崎賢人が演じる公生は天才的なピアニストととして幼いころは名をはせていたが、母親の死を境にしてピアノの音だけが演奏に集中すると聞こえなくなる。かおりは幼いころに公生の演奏を聴いて憧れる。いつか一緒に演奏をしたいという夢を抱き演奏を聴いた後ヴァイオリンを始めた。時が過ぎ、二人は同じ高校に入学する。かおりは公生に話す機会を得られないまま三年生になる。ある時、自分の余命が幾ばくも無いことを知り、思い切って公生に接近を図る。公生が再びピアノを演奏するように導いていくというラブストーリーだ。若者たちの付き合い方があっけらんかとしていて、受験勉強に明け暮れた我々の時代とはずいぶん違うなあと感じた。

しかし、それよりも驚かされたのは、日本の風景がきれいになったなあと思わせてくれたことだ。舞台は湘南、風景からすると鎌倉高校とその近辺のようだ。七里ガ浜、江の島、高校の周辺、公生の家の周辺がとても綺麗だ。かつてカリフォルニアに留学していた時にたびたび訪れたロスアンゼルス近郊のサンタ・モニカの景色にも負けないなと感じた。

気が付かなかっただけだろうと思うが今住んでいる住宅街も子供の頃になじんでいた風景とは大きく変わってとてもきれいになっている。ということで、住んでいる周辺の景色を写真で残しておこうと考えた。今日は、恩田川に沿って散歩をしたのでその風景からだ。この川は鶴見川の支流で横浜線に沿って流れている。小田急線と交わるあたりに源がある。家は町田市と横浜市の境にあるので、その日の気分によって東に下って横浜市側を歩いたり、西に上って町田市側を歩くことにしている。町田市側は桜並木が続き春先はとてもきれいである。この時期は渓谷を思わせる風景だ。
f:id:bitterharvest:20170717154815j:plain

横浜市側は水田や畑があり田園的な風景を醸し出している。夏は日差しが強く散歩には適していないが、今日は曇っていたので田園風景を楽しむために恩田川を下ってみた。田んぼの中を2両連結のこどもの国線の電車がのんびりと過ぎてゆく。
f:id:bitterharvest:20170717144047j:plain

道端には色々な草が咲いている。最近見つけたのがマツヨイグサだ。ツキミソウと間違って呼ばれることもあるが、本当のツキミソウは白い花だ。これに対してマツヨイグサの方は黄色である。
f:id:bitterharvest:20170717144548j:plain
f:id:bitterharvest:20170717145413j:plain

名前が分からないが、ピンク色のとてもかわいい花が咲いていた。
f:id:bitterharvest:20170717145528j:plain
マツヨイグサは家の庭にもこの5月まであった。草と思って間違って抜いてしまったために妻にえらく叱られ、それ以来散歩に出かけるたびにマツヨイグサを探していた。昨日初めて発見し道端の二本を抜いて持ち帰った。うまく育ってくれるとよいのだが。

恩田川に沿って横浜市市民農園がある。季節の野菜が所狭しと植えられている。中にはひまわりを植えている人もいる。
f:id:bitterharvest:20170717145125j:plain

路傍の花も綺麗だ。ツユクサはよく見かけるかわいい花だ。
f:id:bitterharvest:20170717145325j:plain

川には数日前に産まれたのだろう。まだ、泳ぎ方もぎこちない小さなカモの赤ちゃんが群れを成して移動していた。カラスに食べられずに育つといいなあと願って恩田川をあとにした。

伊東丸山公園で紫陽花を楽しむ(7月11日訪問)

地元の歴史研究会の秋の例会で縄文時代の家族システムについて説明することになっている。その準備作業をするために伊豆に逗留することが多くなってきた。インターネットも使えないような山の中で、小鳥の鳴き声を聞きながら緑の木立に囲まれリクライニングチェアを揺らしながら図書館から借りてきた書籍を読み漁っている。期待外れの本がないわけではないが、素晴らしいなと思わせてくれるものの方が多い。
最近読んだ本の中では、ジョナサン・ハイト(Jonathan Haidt)の「社会はなぜ左と右にわかれるのか――対立を超えるための道徳心理学(The Righteous Mind: Why Good People are Divided by Politics and Religion)」でその構想力の大きさに感嘆した。彼は、人間には生得的に与えられた6つのモラル基盤(Moral Foundations)があり、これらにより社会規範が作られていると主張している。どの社会規範が好きなのかはそれぞれの人のそれぞれのモラル基盤の強弱によるといっている。例えば、リベラルな人は6つのモラル基盤の中で「ケア」を大事にし、リバタリアンと呼ばれる人は「自由」を大切にすると説明している。
f:id:bitterharvest:20170717084344j:plain

ハイトのモラル基盤とエマニュエル・トッドの家族システムを利用して、縄文時代の社会がどのようになっていたかを読み解こうともがいているときに、内容は忘れてしまったが何とも薄気味悪い夢を見て真夜中にもかかわらず目覚めてしまった。重い気分の中で何となく耳に違和感を覚えたのもつかの間、奥の方で激しい痛みを感じた。東京に戻ることは難しそうなので、地元の耳鼻科にかかろうということで痛みに耐えながら夜が明けるのを待って、最も近い耳鼻科といっても逗留している場所から20kmも離れている病院に駆け込んだ。

中耳炎になっていると説明を受け、鼓膜を切開した方が直りが早いといわれ、「お願いします」と答えてしまった。麻酔をするから痛くないだろうとたかをくくったのがいけなかった。麻酔液を点耳して10分間待つ。これで終わりだ。外科や歯医者と違って麻酔が効いているかどうかを確認しない。麻酔が効いてないのではという嫌な予感がする。ちょっと我慢してといわれて長い針のようなものを刺される。「ギャ」と言いたいところなのだが歯を食いしばって耐える。その後、膿を吸い出してもらう。最後に、チューブを入れますといわれた。これは予想していなかったことだが、これから耳だれがたくさん出てくるので、それを通すための管ということだった。この挿入もまた痛い。しかし、これで耳の激しい痛みも治まれば報われると前向きに考えて、素直に治療を受けた。

その後、通院を続けている。珍しく妻が伊豆に滞在したときに、少し時期遅れになったが紫陽花の綺麗な場所を探して梅雨時の一日を楽しむこととした。伊東の近くの丸山公園はまだ大丈夫だという情報を得てそこを訪れることにした。

丸山公園は伊東駅から歩いても20分くらいのところにある。伊東の市街地は伊豆急行線の海側の方に開けているが、丸山公園は山側の方にある。携帯のナビを使っていつ着くのかと不安になりながら小沢川沿いの細い坂道を登って行く。住宅街が切れたころ公園の入り口にようやくたどり着いた。
f:id:bitterharvest:20170717093504j:plain

丸山公園は大平の森ハイキングコースの入り口にもなっている。
f:id:bitterharvest:20170717093644j:plain

公園の入り口から少し入るとそこは渓谷の世界である。
f:id:bitterharvest:20170717093824j:plain

沢に沿って紫陽花が植えられている。
f:id:bitterharvest:20170717093943j:plain
f:id:bitterharvest:20170717094017j:plain

ハイキングコースに沿っても綺麗に咲いている。
f:id:bitterharvest:20170717094151j:plain
f:id:bitterharvest:20170717094334j:plain

山百合も負けじと咲いていた。
f:id:bitterharvest:20170717094247j:plain

水辺にも橙色の花(ヒメオウギズイセン)が咲いていた。
f:id:bitterharvest:20170717094712j:plain

丸山公園は梅や桜や紅葉の名所でもあり蛍も飛び交う場所なので、来年もまた訪れて見ようと決意して来た道を引き返した。

関手圏

9.関手圏

9.1 小さな圏の圏

圏は、対象と射で構成されていた。そこで、対象を圏とし、射を関手とする圏を考えることができる。しかし、この圏を作るにあたっては少し制約を設けている。これは圏から圏を作るということになるので、集合の集合を考えるときと同じような問題が生じる。

集合の集合にはいわゆるラッセルのパラソックスと呼ばれえるものがある。集合の集合は集合にはならないというパラドックスである(開集合を定義するときに、有限個の開集合の論理積は開集合と定義しているのを思い出してほしい。無限個の開集合の論理和は開集合と定義しているが、無限個の開集合の論理積については定義していない。これはラッセルのパラドックスを避けるための工夫である。無限個の開集合の論理積を許すとどのような矛盾が生じるかを考えると分かるので試みて欲しい)。

このパラドックスが生じないようにするために、圏から圏を構成するためには、小さな圏を用いて圏を構成するという制約を設ける。

小さな圏とは、それを構成する対象も射も集合であるという制約である(そうでないときは大きな圏という)。この制約を用いれば、関手を用いて圏から圏を構成することができる(大きな圏から作られた圏とならない場合がある)。

小さな圏の圏は\(\rm{Cat}\)で表す。

9.2 関手圏

関手を対象とし、自然変換を射とする圏を作成することを考える。

1) 二つの圏から関手圏を作成する

小さな圏を\(\mathcal{C,D}\)をした時、この小さな圏の間の関手を対象とし、関手のコドメイン間の自然変換を射とした圏は関手圏と呼ばれ、\([\mathcal{C},\mathcal{D}]\)あるいは\(\mathcal{D}^\mathcal{C}\)と表される。

それでは、一般的な関手圏を構成してみよう。次の手順に従って作成していく。

1) 対象を小さな圏\(\mathcal{C},\mathcal{D}\)間の関手\(F,G,H,...\)としよう。
2) 射を\(F\)から\(G\)への自然変換\(\alpha\)、\(G\)から\(H\)への自然変換\(\beta\)、\(F\)から\(H\)への自然変換\(\gamma\),...としよう。
3)自然変換のドメインとコドメインはその成分ごとのドメインとコドメインとなるようにしよう。これは次のようになる。\(\alpha\)の成分\(\alpha_A:F(A) \rightarrow G(A)\)とする。\(F\)のドメインは\(A\)である。これのコドメインを\(A'_F\)とする。この時、\(F:A \rightarrow A'_F\)であり、\(F\)のイメージ\(F(A)\)は\(F(A) \subset A'_F\)となる。同様に、\(G\)に対してもコドメイン\(A_G\)が存在する。そして、厳密に\(\alpha_A\)を定義すると\(\alpha_A:A'_F \rightarrow A'_G\)であるが、本質は失われないので、便宜的に\(\alpha_A:F(A) \rightarrow G(A)\)と書くことにする。
4) 恒等射は同じ関手間での自然変換としよう。例えば、関手\(F\)に対する恒等射を\(id_F\)とする。この成分を\(id_A\)とすると、\(id_A : F(A) \rightarrow F(A)\)となる。即ち、\(F:A'_F \rightarrow A'_F\)であり、\(F(x \in A'_F) = x\)である。
5) 自然変換の合成を成分での合成にしよう。例えば、\(\gamma=\alpha \circ \beta\)を次のように成分ごとに定義する。即ち、\(\gamma_A=\alpha_A \circ \beta_A\)とする。

言葉ではわかりにくいので、図で示すことにしよう。対象\(F,G\)と射\(\alpha\)の成分\(\alpha_A\)を示したのが下図である。1)で構成した対象は関手\(F,G\)であり、2)で構成した射は自然変換\(\alpha\)であり、その成分は\(\alpha_A\)である。
f:id:bitterharvest:20170609214635p:plain
この図で次のことに注意してほしい。\(F(A)\)は関手のイメージであり、関手のコドメイン\(A'_F\)は\(F(A) \subset A'_F\)である。同様に、関手\(G\)のコドメイン\(A'_G\)とすると、自然変換\(\alpha\)の成分\(\alpha_A\)は\(\alpha_A : A'_F \rightarrow A'_G\)である。

5)の射の合成は下図のようになる。図では、自然変換\(\alpha\),\(\beta\)の合成が自然変換\(\gamma\)となる様子を示したものである。即ち、それぞれの成分\(\alpha_A\),\(\beta_A\)の合成が\(\gamma\)の成分\(\gamma_A\)となる。
f:id:bitterharvest:20170609222131p:plain

さらに、圏\(\mathcal{C}\)の対象\(A\)が射\(f\)によって対象\(B\)によって写像されるときの、自然変換の関係を示したのが下図である。ここで、\(\alpha_A\)と\(\alpha_B\)は、自然変換\(\alpha\)の成分である。即ち、\(\alpha_A\)はドメインを\(A\)とした成分で、\(\alpha_B\)はドメインを\(B\)とした成分である。\(\beta\),\(\gamma\)についても同様である。
f:id:bitterharvest:20170609222547p:plain
上記の図は可換図式となっていることに注意してほしい。即ち
\begin{eqnarray}
\alpha_B \circ F(f) &=& G(f) \circ \alpha_A \\
\beta_B \circ G(f) &=& H(f) \circ \beta_A \\
\gamma_B \circ F(f)=\beta_B \circ \alpha_B \circ F(f) &=& H(f) \circ \beta_A \circ \alpha_A = H(f) \circ \gamma_A
\end{eqnarray}

また、4)の恒等射を示したのが下図である。
f:id:bitterharvest:20170609222822p:plain

上記の手順によって構成したものが圏となるためには、単位律と結合律を満足しなければならない。

単位律は成分ごとに\(id_A \circ \alpha_A = \alpha_A = \alpha_A id_A\)を示せばよい。なお、前者の\(id_A\)は\(id_A: G(A) \rightarrow G(A)\)であり、 後者の\(id_A\)は\(id_A: F(A) \rightarrow F(A)\)である。

結合律は、自然変換\(\alpha,\beta,\gamma\)が与えられた時、\((\alpha \circ \beta) \circ \gamma=\alpha \circ (\beta \circ \gamma)\)が成り立つことを示せばよい。これは、成分ごとに、即ち、\((\alpha_A \circ \beta_A) \circ \gamma_A=\alpha_A \circ (\beta_A \circ \gamma_A)\)が成り立つことを示せばよい。

2) 関手圏の小さな圏の圏の一員

関手圏は、小さな圏から構成された圏(先の例では\(\mathcal{C,D}\)から構成された圏)と見なすことができるので、小さな圏の圏\(\rm{Cat}\)の一員である。
f:id:bitterharvest:20170610114515p:plain

この圏では、図に示すように、関手を1-cellと呼び、自然変換を2-cellと呼ぶ。また、圏は0-cellと呼ばれる。自然変換を2-cellを射とした圏を2-圏(2-category)という。また、関手圏を小さな圏の対象とすることもできる。これにより、高次の圏を構成できる。

3) 三つの圏から関手圏を作成する

先の例では、二つの圏から関手圏を構成したが、今度は三つの圏から関手圏を構成することを考える。言葉で示すよりも図で示したほうが分かりやすいので、そのようにする。
f:id:bitterharvest:20170610104754p:plain



圏\(\mathcal{C}\)から圏\(\mathcal{D}\)への関手を\(F,F'\)とする。また、\(F\)から\(F'\)への自然変換を\(\alpha\)とする。同様に、圏\(\mathcal{D}\)から圏\(\mathcal{E}\)への関手を\(G,G'\)とし、\(G\)から\(G'\)への自然変換を\(\beta\)とする。この時、対象を\(F,F',G,G'\)とし、射を\(\alpha,\beta\)とした関手圏が上手に示すものである。

また、\(\mathcal{C}\)で対象\(A\)から\(B\)への射を\(f\)とした時、下図のような可換図式を得ることができる。
f:id:bitterharvest:20170610112132p:plain
自然変換としての条件がどのように満たされているかは、とても煩雑な作業だが、読者の方で確認して欲しい。

9.3 圏論をさらに探求すると

以下では、圏論をさらに進めるとどのような世界が開けてくるのかについて簡単に説明する。

1) Bicategory

Bicategoryは2-圏(2-category)の条件を緩めたものである。2-圏では、単位律と結合律が成り立つことを必要条件としていたが、これを同型写像でよいとしたものである。即ち、単位元については、\(id \circ \alpha\)と\(\alpha \circ id\)の間に同型写像が存在すればよいとしたものである。結合律についても、\((\alpha \circ \alpha) \circ \gamma\)と\(\alpha \circ (\alpha \circ \gamma)\)の間に同型写像が存在すればよいとしたものである。

2) Homotopy Type Theory

圏では、射は一方向であった。射が両方向であることを条件に圏と同じような構造を構造を立てることができる。これは圏に代わってGroupoidと呼ばれる。2-categoryを定義したときと同じようにGroupoidの上に高次のGroupoidを構成することができる。高次が無限のレベルになるとHomotopy Type Theoryと呼ばれるものになる。