bitterharvest’s diary

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

米田の補題 ー Haskellで理解する

6.4 Haskellで理解する

米田の補題補題を下図の可換図式を用いて説明した。Haskellを用いてさらに理解を深めることとしよう。
f:id:bitterharvest:20180326210741p:plain

Bartosz Milewskiさんが上手に説明しているので、それを借りることにしよう。\({\rm Hom}\)関手を作成しよう。\(Reader\)のデータ型を定めよう。これは、射の集合だ。そこで、ドメインを型変数\(a\)で、コドメイン\(x\)とし、\(a\)から\(x\)への射の集まりと考えると、型構築子を\(Reader\)として、次のように定義できる。

type Reader a x = a -> x

\(Reader\)は関手なので、クラス\(Functor\)のインスタンスとするためには、次のようにする。

instance Functor (Reader a) where
  fmap f g = f . g

プログラムは下図を意味する。
f:id:bitterharvest:20180403162555p:plain

そして、自然変換\(\alpha\)は多相関数なので、次のように定義できる(ドメインやコドメインの方が型変数になっているものを多相関数という)。

alpha :: forall x . (a -> x) -> F

これは、以下の可換図式と同じである。
f:id:bitterharvest:20180403162624p:plain

米田の補題は、

forall x . (a -> x) -> F ~= F a

と言っている。即ち、自然変換と\(F \ a\)の要素は一対一に対応すると言っている。

Haskellでは、右の項の\(F \ a\)は通常、データ型として扱う。そして、\(F\)は\(a\)を包んでいるコンテイナと考える。左の項は、引数として関数をとる、多相関数だ。米田の補題は、二つの項が同値だと言っている。即ち、同じ情報を有していると言っている。

言い方を変えると、

alpha :: forall x . (a -> x) -> F

という多相関数が与えられると、\(a\)のコンテイナ\(F\)を得ることができる。逆に、\(a\)のコンテイナ\(F\)が与えられる、先の多相関数を得ることができると言っている。

米田の補題の証明では、\(A\)から\(A\)への恒等射\(id\)を用いた。証明の手順を追っていこう。

\(id_A\)に対して、\(F (A)\)の任意の一つの要素\(p \in F(A)\)を対応させよう。即ち、

alpha id :: F a
p :: F a

\(id_A\)を\(p\)に写す自然変換を\(\alpha\)とすると、下図の可換図式を得る。
f:id:bitterharvest:20180406095514p:plain

従って、

alpha f = fmap f p

を得る。
逆に、\(p\)を任意にするのではなく、\(f\)を任意にしてもよいことが分かる。これにより米田の補題が証明されたこととなる。

米田の補題の応用の一つは、継続渡しスタイル(continuation-passing style)のプログラムである。興味のある人は調べて欲しい。

御殿場線山北駅で桜並木を楽しむ

今年は、桜が開花してから、暖かいというよりも暑い日が続き、あっという間に満開となってしまった。まごまごしていると、良い時期を失ってしまいそうだ。今日(29日)も天候に恵まれたので、少し遠くでさらにマニアックだが、御殿場線山北駅に出かけた。

山北町は神奈川県西部の丹沢山地にある町だ。人口は1万人強。山北駅明治22年に開業している。現在は、御殿場線だが、かつては東海道本線の主要な一駅であった。昭和9年丹那トンネルが開通すると、御殿場線という地方路線の一つの駅になった。

山北駅へは、小田急線で新松田まで行き、御殿場線松田駅を利用した。
f:id:bitterharvest:20180329182916j:plain
f:id:bitterharvest:20180329182937j:plain

松田駅から山北駅までは2駅だ。ただし、列車は1時間に1~2本とまばらだ。いつもは乗客は少ないのだろうが、今日は、同じ目的を抱いている人が多く集まっているようで、ホームではかなりの人が列車を待っていた。

昼間はワンマンカ―なので、駅員が常駐していない山北駅では2両連結の先頭車両の一番前のドアーからしか降りることができない。いつもはスムーズに降りられるのだろうが、今日は降りる人の列が延々と続いた。列車が出発する前にホームのはずれに行き、桜並木に向かう列車を撮影できるように構える。橋の上にもこの瞬間を目指してカメラマンたちが構えている。
f:id:bitterharvest:20180329183047j:plain

駅の周辺は公園になっており、D52型蒸気機関車が展示されていた。
f:id:bitterharvest:20180329183118j:plain

御殿場線は、山北駅から西側の方は掘割になっている。底を列車が走り、土手に沿っておよそ130本の桜並木がある。
f:id:bitterharvest:20180329183159j:plain

土手には野草も咲いていて、楽しみを高めてくれる。
f:id:bitterharvest:20180329183250j:plain
f:id:bitterharvest:20180329183325j:plain

でも、ここに来た目的は列車と桜並木を一緒に撮ることだ。多くの人も列車の時刻表を見ながら、今か今かと待っている。我々もその仲間に入って、機会を狙った。
f:id:bitterharvest:20180329183411j:plain

もう少し待つと特急列車が通るのだが、お腹もすいてきたので山北駅を去ることにした。懐かしい赤いポストにも別れを告げた。
f:id:bitterharvest:20180329183505j:plain

手元に大正14年の時刻表があったので、当時の東海道本線の様子を調べてみた。東京発の特急列車は2本で、この頃は愛称で呼ばれていない。特1、特3となっている。特1は一等車・二等車のみだ。東京駅を8:45に出発し、横浜、国府津だけに停車した後、山北には11:02に到着する。ここからは勾配のきつい区間になるため、補助機関車を接続して、次の沼津駅へと向かう。

現在、御殿場線には新宿発のふじさん1,3,5号の特急電車が運行されているが、山北には停車しない。ふじさん3号は、新宿を10:40に出発し、松田に11:58に到着し、所要時間が半分になっている。蒸気機関車から電車へと変化し、時代の流れを感じさせられるが、時々は、山北駅D52型蒸気機関車を走らせてくれたらと思う。

川崎市の日本民家園を訪ねる

川崎市の日本民家園が50周年を迎えていて、その記念のコンサートをしたという記事を週末に見た。春の温かい日差しに恵まれ、また、懐かしさも手伝って、昨日(27日)訪れた。田園都市線と南部線が交差する溝の口から向ヶ丘遊園行のバスを利用した。最近のバスはどこでもそうなのだが、老人たちで混んでいる。市が提供する無料パスを利用する人々が多いためだ。この日に利用したバスも例にもれず、市外から来た我々夫婦を除いては、無料パスを見せながら、運転手席の横にある料金箱をパスしていく。

向ヶ丘遊園行は二系統あって、我々のバスは府中街道に沿って進む。この街道に沿って二ヶ領用水が流れている。川に沿って、花や木が植えられていて、車窓を楽しませてくれる。この用水は、江戸時代が始まる頃に農業用水として造られたもので、川崎領と稲毛領にまたがったことから二ヶ領と呼ばれたということを、川崎市内の小学校で4年生と5年生を過ごした頃に学んだ。桜で有名な二ヶ領用水の方はこれの分流だ。

最寄りの停留所でバスを降り、民家園へと向かう。車通勤をしていた頃に抜け道に使っていた道で、付近の丘陵地は一方通行で狭く対向車が来ないことを祈りながら運転したものだが、いまでは、歩道も付いた立派な道になっている。

日本民家園は、生田緑地と呼ばれる広大な丘陵地の一角にある。この緑地には、このほかに、岡本太郎美術館藤子・F・不二雄ミュージアムなどがあり、入口(東口)にはビジターセンターもある。
f:id:bitterharvest:20180328205749j:plain

日本民家園には、18軒の住宅と、水車小屋や蔵など7軒の建物がある。合わせて25軒の展示民家だ。五つにグループ分けされ、入口の方から、宿場、信越の村、関東の村、神奈川の村、東北の村となっている。また、国の重要文化財に指定されている住宅も7軒ある。

入り口から入ってすぐのところに、番号0、どのグループにも属さない原家住宅がある。大正時代に竣工したため、江戸時代の他の建物とは離して移築したのだろう。
f:id:bitterharvest:20180328205840j:plain

原家住宅のすぐ横は、宿場だ。門の右側に見えるのは、鈴木家住宅で、奥州街道の八丁目にあった馬宿だ。白河での子馬の競り市に往復する馬喰と馬方が泊まっていた。
f:id:bitterharvest:20180328205916j:plain

さらに奥へと進むと、佐地家の門・供待・堀がある。250石取り尾張藩士の武家屋敷入口部分だ。今年の大河ドラマは「せごどん」だ。その主人公は西郷隆盛だが、父の吉兵衛は下級武士で、石高は41石である。家老の小松帯刀は2600石だ。1石で一人暮らせる時代だった。中級武士というところだろうか。
f:id:bitterharvest:20180328210006j:plain

宿屋地区の最後は、伊奈街道の宿駅、伊那部宿にあった三澤家住宅だ。農業を主とし、代々組頭をつとめてきた村の有力者の家である。板葺の屋根で、置石があるのが特徴だ。
f:id:bitterharvest:20180328210110j:plain

次は、信越の村だ。小学6年から中学2年まで、長野で暮らしたので、親しみの持てる場所だ。最初の建物は長野県南佐久郡のもので、千曲川沿いの名主の家である。長野県と言うだけで雪深いところと思いがちだが、そうではない。左側が佐々木家だ。柱の細さに気が付く。そして、正面が江向家住宅だ。富山県岐阜県との境にある越中五箇山の合掌造りの家だ。豪雪地帯のため、雪の重みに耐えるために様々な工夫が凝らされている。二つの家とも国の重要文化財に指定されている。このほかにも、越中五箇山の合掌造りの家が2軒、飛騨白川郷の合掌造りの家が1軒ある。また、白川郷の家はお蕎麦屋さんになっていたので、ここで、お昼を食べた。
f:id:bitterharvest:20180328210154j:plain

腹ごしらえも済んだところで、関東の村へと進んだ。これまでとは趣を異にする家に出くわす。千葉県の九十九里浜から移築された作田家住宅だ。網本の家で、棟(屋根)が、横の方に向かうものと、奥の方に向かうものとに分かれていることが分かる。分棟型民家と呼ばれている。この家も国の重要文化財である。
f:id:bitterharvest:20180328210223j:plain

次の廣瀬家住宅の家は、妻の部分の柱が曲がっていて、目を楽しませてくれた。山梨県甲州市の民家だ。
f:id:bitterharvest:20180328210307j:plain

今度は、茨城県笠間市の名主さんの家だ。大戸の上に定書きがあり、それを読もうとしていたら、ボランティアの人が近づいてきて、助けてくれた。定書きは慶應4年に太政官(明治政府)から出されたもので、次のように書かれていた。なお、変体仮名のところはカタカナに変えてある。
一 人タルモノ五倫ノ道ヲ正シクスヘキ事
一 鰥寡孤獨癈疾ノモノヲ憫ムヘキ事
一 人ヲ殺シ家ヲ焼キ財ヲ盗ム等ノ惡業アル間敷事

この建物も分棟型民家で、国の重要文化財に指定されている。ボランティアの方の説明によれば、太田家住宅はここで火災に遭遇したそうだ。1990年の夏、近くの公園で遊んでいた若者たちの打ち上げ花火が屋根に落ち、母屋を中心に焼けてしまった。その後、復元されたが、焼けた跡が柱に残っていると言って、見せてくれた。文化財の保護の難しさの一面を知ることができた。
f:id:bitterharvest:20180328210446j:plain

次は、神奈川の村だ。最初に現れるのが、秦野市の名主をしていた北原家住宅だ。移築解体をするときに、墨書が発見され、貞享4年(1667年)に建築されたことが分かる貴重な家で、国の重要文化財となっている。
f:id:bitterharvest:20180328210530j:plain

そして、いよいよ伊藤家住宅だ。日本民家園発祥の建物だ。関口欣也さんは、1955年に横浜国立大学で農村の近代化の推進についての卒業論文に取り組んでいるときに、江戸時代に名主であった伊藤家の古民家に出会う。建築史の大岡実教授にも相談して、古民家の建築時期を判断しようとしたが、寺や城などと違い、その当時は古民家の基準を示すものがなかった。大岡教授はそのあと古民家の調査に乗り出し、伊藤家住宅は17末~18世紀初ということが1960年に判明した。歴史的に貴重なこの住宅を保存するために、国重要文化財に指定し、横浜の三渓園に移築する方針が決まった。

川崎市文化財を担当していた古江亮仁さんは、その計画を1964年に知り、大岡教授を訪ね、川崎市に残せないかと相談する。民家園の構想が持ち上がり、それが現在へとつながった。1967年4月にオープンしたが、何ともさみしいことに、初日の入館者は0だったそうだ。しかし、2016年度の実績は116,053人となっている。
f:id:bitterharvest:20180328210628j:plain

最後は東北の村。村の構成が分かりにくかったことと、疲れてきたこともあって、国重要文化財に指定されている工藤家住宅を見学しそこなったが、山形県鶴岡市の菅原家住宅は、見ごたえがあった。豪雪地帯のため、冬用の出入り口が二階にあり、雪の重みで家が沈むために、引き戸にはコロが付いていた。酷寒に耐えるには隙間が多すぎるように感じたが、わらの中で睡眠するとのこと、耐え忍んだのだろうと哀れさを感じた。
f:id:bitterharvest:20180328210732j:plain

妻の目的である桜見学を達成していなかったので、出口で警備の人にこの辺に桜の見どころはないだろうかと聞くと、舛形城がいいのではと言われた。180段の階段を上った先に、お城の跡がある。かつては広場だけだったような気がするが、いくつかの施設が建築されていた。入口には門があった。
f:id:bitterharvest:20180328210814j:plain

舛形城は鎌倉時代に、秩父党の流れをくむ小山田有重の子の稲毛三郎重成によって築かれたとされている。広場には見晴らし台がある。
f:id:bitterharvest:20180328210851j:plain

見晴らし台から見た桜がきれいだった。桜は見上げることが多いのだが、見下ろすのもなかなか壮観だ。
f:id:bitterharvest:20180328210917j:plain

帰りは向ヶ丘遊園駅まで歩くことにした。途中に、生田長者穴横穴墓があるので見学した。7世紀の築造で、10程度の横穴がある。勾玉を始めとする副葬品も出土している。
f:id:bitterharvest:20180328211020j:plain

天気にも恵まれ、夫婦二人、まぶしいほどに明るい春を楽しむことができた。

米田の補題 ー 証明

6.3 米田の補題の証明

前回の記事で米田の補題を提示した。米田の補題は次のようになっていた。

局所的に小さな圏\(\mathcal{C}\)において、\({\rm Hom}\)関手\(h^A: \mathcal{C} \rightarrow \mathbf{Set}\)から、集合値関手\(F: \mathcal{C} \rightarrow \mathbf{Set}\)への自然変換と、集合である対象\(F(A)\)の要素との間に一対一の対応が存在する。なお、\([\mathcal{C},\mathbf{Set}]\)は関手圏で、\({\rm Hom}\)関手と集合値関手はその対象であり、自然変換はその射である。

これを式で書くと、
\begin{eqnarray}
[\mathcal{C},\mathbf{Set}](h^A,F) \cong F(A)
\end{eqnarray}

\({\rm Hom}\)関手\(h^A\)と集合値関手\(F\)では、自然変換\(\alpha\)に対して、下図の可換図式が成り立つ。
f:id:bitterharvest:20190527135127p:plain
そこで、上の図で、\(X\)を\(A\)とし、\(Y\)を\(X\)とすると、下図の可換図式を得る。
f:id:bitterharvest:20190527135751p:plain
このとき、これは単に可換図式だと述べているだけではない。米田の補題は「\({\rm Hom}\)関手\(h^A\)から集合値関手\(F\)への自然変換と、集合である対象\(F(A)\)の要素との間に一対一の対応が存在する」と言っている。隠れていたものが表れてくるのがこの補題だ。

1)可換図式であることの証明

それでは、米田の補題を証明する前に、最初に揚げた図が可換図式になっていることを証明しよう。それには自然変換が存在することを証明すればよい。

もう少し詳しく説明すると、\(F\)を圏\(\mathcal{C}\)から圏\(\mathbf{Set}\)への関手とする。関手であることから、任意の\(a \in A\)に対して、任意の関数\(g:A \rightarrow X\)と任意の\(f:X \rightarrow Y\)適応したとする。このとき、圏\(\mathcal{C}\)内で関数\(g,f\)を施して、圏\(\mathbf{Set}\)へ移した\(F (f (g (a)))\)と、\(a\)を圏\(\mathbf{Set}\)に写し、その後、関数\(F(g),F(f)\)を施した\(F(f) (F(g) (F(a)))\)とは、一致する。これを利用して、可換にするような、\(\alpha = \{\alpha_X : \mathcal{C} (A,X) \rightarrow F(X), \alpha_Y : \mathcal{C} (A,X) \rightarrow F(X) \}\)が存在することを証明すればよい。

そのために、下図を利用しよう。この図は、説明しやすくするために、圏\(\mathcal{C}\)を圏\(\mathbf{Set}\)の上に描いた。なお、\(p = F(a)\)とした。
f:id:bitterharvest:20190527135824p:plain
最初に1から2を経由して3への写像は考えよう。1での一つの要素\(g \in \mathcal{C}(A,X)\)は2での要素\(f \circ g \in \mathcal{C}(A,X)\)に写される。これは3の中のある一つの要素に写すことを考えよう。この要素を\(F ( f \circ g) (p) \in F(Y)\)としよう。2から3への射を考えることができるので、これを\(\alpha_Y\)としよう。これまでの説明を式で表すと次のようになる。
\begin{eqnarray}
&& \alpha_Y \circ \mathcal{C}(A,f)(g) \\
&=& \alpha_Y (f \circ g) \\
&= & F(f \circ g) (p)
\end{eqnarray}

\(F\)が関手であることを利用して式を展開すると
\begin{eqnarray}
&= & F (f) \circ F (g) (p) \\
&= & F (f) (F (g) (p))
\end{eqnarray}

ここで、\(F (g) (p) \in F(X)\)である。即ち、\(F(X)\)の一つの要素である。そこで、1から3への写像を考えることができる。そこで、これを\(\alpha_X\)としよう。さらに式の展開を続けると、
\begin{eqnarray}
&= & F (f) (\alpha_X (g) ) \\
&= & F (f) \circ \alpha_X (g)
\end{eqnarray}
を得る。これから、任意の\(f,g\)に対して、
\begin{eqnarray}
&& \alpha_Y \circ \mathcal{C}(A,f)(g) \\
&= & F (f) \circ \alpha_X (g)
\end{eqnarray}
とするような自然変換\(\alpha=\{\alpha_X,\alpha_Y\}\)が存在することが分かる。

2)米田の補題の証明

それでは、米田の補題を証明しよう。1対1に対応することから次のように行えばよい。

1)\(F(A)\)内の要素を一つ定めたとしよう。この要素に対して、\(h^A\)から\(F\)への自然変換がただ一つ存在することを示す。

2)\(h^A\)から\(F\)への自然変換を一つ定めたとしよう。この自然変換に対して、\(F(A)\)内の要素がただ一つ存在することを示す。

1)の証明を行うことを考えよう。証明には下図を利用しよう。
f:id:bitterharvest:20190527135909p:plain
\(F(A)\)内の要素を一つ定めたとする。これを\(p\)としよう。これは任意の点であることに注意して欲しい。そして、\(h^A\)から\(F\)への求める自然変換を\(\alpha:h^A \rightarrow F\)としよう。自然変換は成分ごとに定められるので、\(\mathcal{C}(A,A)\)から\(F(A)\)への成分を\(\alpha_A\)としよう。また、任意の対象\(X\)に対して、自然変換の成分を\(\alpha_X\)としよう。そして、\(\alpha_A\)と\(\alpha_X\)がただ一つ存在することを示せばよい。

まず、\(\alpha_A\)について考えよう。\(\mathcal{C}(A,A)\)は恒等射であるため、\(id_A = \mathcal{C}(A,A)\)である。従って、単一の射\(id_A\)から単一の要素\(p\)へ写像させるただ一つの\(\alpha_A\)が存在する。

次に、\(\alpha_X\)について考えよう。\(\mathcal{C}(A,f) : g \rightarrow f \circ g\) である。ここで、\(g \in \mathcal{C}(A,A)\), \(f \circ g \in \mathcal{C}(A,X)\)なので、\(g=id_A\)より、\(f = \mathcal{C}(A,X)\)となる。他方、\(p\)は、射\(F(f)\)によって\(F(X)\)のある一つの要素に写像される。この点を\(q \in F(X)\)としよう。従って、
\begin{eqnarray}
q=F (f) (p)
\end{eqnarray}
となる。

一方、\(f = \mathcal{C}(A,X)\)から\(q\)への変換が存在する。これを\(\alpha_X\)とする。即ち、
\begin{eqnarray}
q=\alpha_X (f)
\end{eqnarray}
である。

これより、
\begin{eqnarray}
\alpha_X (f) = F (f) (p)
\end{eqnarray}
となり、\(\alpha_X\)が一意に定まることが分かる。

従って、 \(F(A)\)の一つの要素に対して、成分を\(\alpha_A\), \(\alpha_X\)とするようなただ一つの自然変換\(\alpha\)が存在することが証明できた。

同様に、任意に与えた一つの自然変換\(\alpha\)に対して、\(F(A)\)の要素がただ一つ存在することも証明できる。

よって、米田の補題は証明された。

米田の補題 - 米田の補題とは

6.2 米田の補題とは

米田の補題を説明する前に、圏論とは一見関係のなさそうなカニと大相撲の例について説明した。そこでの説明で重要な事項は、操作ボタンと機械の動作が一対一に対応していることだ。米田の補題は、このような状況を、数学的に説明したものだ。分かってしまうと何だということになるのだが、そうは言っても、理解しにくいことに変わりはない。それでは、米田の補題の説明を始めよう。

下図を用いて、いつもよりは少し厳密に説明しよう。
f:id:bitterharvest:20190527135127p:plain
まず、上図の左側には、圏\(\mathcal{C}\)を描いた。この圏は、その構造を知りたいと思っている圏だ。具体例でいえば、リモコンのカニあるいは大相撲の世界だ。

\(A\)から\(B\)への写像を集めたものを\({\rm Hom}(A,B)\)と書くことしにしよう。そして、圏\(\mathcal{C}\)に対して少し制限を設けることにしよう。\(\mathcal{C}\)では、各対象\(A,B\)に対して、\({\rm Hom}(A,B)\)は集合になっているとする(即ち、\({\rm Hom}(A,B)\)は集合となるには大きすぎる真のクラス(proper class)ではないとする)。このとき、\(\mathcal{C}\)は、局所的に小さい圏と呼ばれる。

図では、対象\(A,X,Y\)を用意し、対象\(A,X\)と対象\(A,Y\)の間には、\({\rm Hom}\)集合を描いた。また、対象\(X,Y\)の間には一つの写像\(f\)を用意した。

右側には、圏\(\mathbf{Set}\)を描いた。この圏については、我々は熟知しているので、ここに写してくると取り扱いやすくなると期待している空間だ。圏\(\mathcal{C}\)では隠れていて見えないのだが、圏\(\mathbf{Set}\)に写すことにより、隠れていたものが見えてくるようになると期待している空間だ。

圏\(\mathcal{C}\)から圏\(\mathbf{Set}\)へ移動させるためには、圏論の常套手段だが、関手を用いる。そこで、圏\(\mathcal{C}\)の対象を一つ選び、その対象に対して二つの関手を設定することにしよう。ここでは、対象\(A\)を固定することにする。

一つ目は\({\rm Hom}\)関手である。図では、\(A\)からの\({\rm Hom}\)集合を移す\(\mathcal{C}(A,-)\)を描いた。この関手\(\mathcal{C}(A,-)\)により、任意の\(X\)に対して、圏\(\mathcal{C}\)の\({\rm Hom}(A,X)\)は圏\(\mathbf{Set}\)の\(\mathcal{C}(A,X)\)に写される。また、\(X\)からの任意の射\(f\)は\(\mathcal{C}(A,f)\)に写される。

二つ目の関手\(F\)は、集合値関手(set-valued functor)と呼ばれるものだ。\(\mathcal{C}(A,-)\)からの自然変換によって得られるものである。前回の記事で説明した表現可能関手はその一つの例である。表現可能関手は\(\mathcal{C}(A,-)\)に同型であったが、集合値関手はそこまで要求せず、自然変換でよい。

さて、二つの関手\(\mathcal{C}(A,-)\)と\(F\)が得られたので、それを対象にして新たな圏を作ろう。関手圏だ。この圏を\([\mathcal{C},\mathbf{Set}]\)としよう。また、\(\mathcal{C}(A,-)\)は、関手らしく見せるために、\(h^A\)と記述することにしよう。

新たな圏\([\mathcal{C},\mathbf{Set}]\)は下図のようになる。また、\(h^A,F\)の間の射は、自然変換\(\alpha\)である。また、\(h^A\)は次式を成り立たせている。
\begin{eqnarray}
& h^A :& X \rightarrow \mathcal{C}(A,X) \\
& h^A :& f \rightarrow \mathcal{C}(A,f) \\
& where & \ \mathcal{C}(A,f): g \rightarrow f \circ g, \\
&& g \in \mathcal {C}(A,X), f \circ g \in \mathcal {C}(A,Y)
\end{eqnarray}
f:id:bitterharvest:20190527135146p:plain
さて、準備が整ったので、米田の補題を示そう。
\begin{eqnarray}
[\mathcal{C},\mathbf{Set}](h^A,F) \cong F(A)
\end{eqnarray}

上の式は次のことを言っている。\(h^A\)から\(F\)への自然変換は、\(F(A)\)と同型である。即ち、\(F(A)\)の一つ一つの要素に対して、自然変換が一つずつ対応する。逆に、自然変換の一つ一つに対して\(F(A)\)の要素が一つずつ対応する。なお、\([\mathcal{C},\mathbf{Set}]\)は関手圏で、\({\rm Hom}\)関手と集合値関手はその対象であり、自然変換はその射である。

これは、前回の記事で説明した内容とよく対応している。前回の記事で説明した\(F(A)\)の要素はボタンである。また、自然変換は、モーターの一つの回転(左、右、あるいは、停止)であり、再生の画面である。米田の補題と具体例が結びついたので、次回は、米田の補題を証明しよう。

米田の補題 ー 具体例

6.米田の補題

今回の記事は、米田の補題(Yoneda lemma)である。圏論の定義の中で、個人名がついている定理や補題はそれほど多くない。これから紹介する補題は、数少ない例の一つである。そして、重要な定義であることには間違いない。それも、かなり難解なものと想像してもよいだろう。でも、圏論のすばらしさを知るためには、この補題の理解を欠かすことはできない。

米田の補題はとても抽象的な理論であるが、この理論を正確に理解するためには、具体的な応用例が必要だ。そこで、今回もまた、具体的な例を求めて、長い時間をかけて検討した。やっとまとまったので紹介しよう。

6.1 具体例

1) 横歩きするカニ

リモコンからの遠隔操作でおもちゃを動かして楽しんでいる人も多いと思う。ラジコン飛行機、レーシングカー、最近では、ドローンだ。コントロールボタンを操作することで、これらの機器が自由に動き回る。何とも面白いおもちゃだ。これらのおもちゃが米田の補題を説明するのに丁度良い道具立てだ。ここで取り上げるのは、横歩きするカニのおもちゃだ。カニのお腹にはモーターがついていて、それが回転することで、左あるいは右に動くものとしよう。モーターの回転は、リモコンのボタンで決めているものとしよう。

横歩きするカニの働きを、米田の補題が説明できるように、下図のように表すことにしよう。
f:id:bitterharvest:20180324145043p:plain

左上にあるのは、「カニのおもちゃ」そのものである。ここではとりあえず、静止していることにしよう。左下にあるのはリモコンで、ボタンを押すと、カニが左に移動したり、右に移動したり、あるいは静止したりする。

右下はカニの現在の状態を表している。ここでは、「右」というボタンが押され、カニが道路際を右に移動している様子を示している。

右上はモーターの動作を表している。モーターは左に回転したり、右に回転したり、停止したりする。この動作はリモコンのボタンが押されたことで定まる。今「右」というボタンが押されているので、右の方に回転している。

図には、米田の補題を説明するときのために、式を入れてあるがここでは気にしないでおこう。

ここで重要なことは、それぞれのボタンにモーターの動作が対応しているということだ。リモコンには3種類のボタンがあるが、モーターの動作も同じく三種類ある。

ボタン押せばそれによってモーターの動作が決まり、逆に、モーターの動作を見ると押されているボタンが分かる。そして、モーターの動作からも、押されたボタンからも、現在のカニの様子が分かる。

リモコンのボタンは、圏論的に考えると、対象と言えるだろう。それに対して、モーターの動作は、射と思えるだろう。米田の補題については、後で詳しく説明するが、対象と射が一対一に対応することがその本質である。

それでは、「左」のボタンが押された時の様子を下図に示そう。同じように描けることが分かる。
f:id:bitterharvest:20180324145114p:plain

2) 大相撲

先の説明は、カニが一匹だったため、比較的、考えやすかったと思う。それでは、対象の構成要素が一つではなく、複数あった場合について考えてみよう。

丁度、大相撲が開催されているので、それを例にしてみよう。大相撲では、二人の力士が相撲を取ることを基本的な構成要素としている。この構成要素は先の一匹のカニに相当する。そして、誰と誰が対戦するのかを示す取組表が与えられる。この部分はカニがたくさんいることを表している。従って、複数のカニがいる場合を考えるのと同じこととなる。

今、対戦毎に相撲を取った時の状況が録画されているとしよう。そして、それぞれの対戦は再生できるようになっているとしよう。その選択はボタンによって行えるものとしよう。カニの場合と同じように、この状況を下図のように描いてみよう。
f:id:bitterharvest:20180324145130p:plain

左上はそれぞれの対戦に対する録画である。「左下」はコントローラで、対戦毎にボタンが設定されている。図では、鶴竜と栃ノ心の対戦に対するボタン、逸ノ城豪栄道の対戦に対するボタン、高安と千代丸の対戦に対するボタンがある。いずれかのボタンを押すと、その対戦が再生される。

右上はボタンにより選択された対戦が再生されている様子を示したものである。右下は結果である。上の図では鶴竜と栃ノ心の対戦が選択されたので、その結果が表示され、他の結果は隠されてたリスト(左側)が提示される。また、逸ノ城豪栄道のボタンが押されたならば、その結果を示したリスト(右側)が示される。

大相撲の録画を見るという条件設定では、上の図で十分なような気がするが、米田の補題を説明するためには不備である。

上の図では、一つの対戦しか見ることができなかった。これは厳しすぎる制限のように思える。まったく、制限のない状態ではどのように考えたらよいのだろうか。いくつも一緒に再生できるとしたらどうであろうか。これまで説明してきたこともこの中には含まれるし、二つや三つを同時に見たいということはありうるかもしれないし、もっとたくさん同時にということだってありうる。そこで、希望するだけの対戦を同時に見ることができるというように条件設定を変えてみると下図を得る。
f:id:bitterharvest:20180324145154p:plain

この図では、左下のボタンの数がとても増えている。左側のボタンの列は一つの対戦を選ぶものだ。例えば、鶴竜と栃ノ心の対戦に対するボタン、逸ノ城豪栄道の対戦に対するボタンとなっている。その右側の列は二つの対戦を選ぶものだ。例えば、鶴竜と栃ノ心の対戦と逸ノ城豪栄道の対戦の二つを選ぶボタンである。描いてはないが、その右横の列は三つの対戦を選ぶ、さらには、四つの対戦を選ぶというようになる。また、これらの中には、全く選択しないというボタンも配置されている。

また、右上の再生も一つではなく、複数を再生している場合もある。この図では、鶴竜と栃ノ心の対戦と逸ノ城豪栄道の対戦を二つ同時に再生するというボタンが押されている場合を想定している。このとき、右上には、これらの対戦が再生される。また、右下では、再生されたもの、あるいは、ボタンで選ばれた対戦の結果を示したリストが提示される。なお、このリストには、そのほかの対戦の結果は記述されていない。

大相撲の例でも、ボタンと再生が一対一に対応していることに注意して欲し。

二つの具体例を示すことで、米田の補題を示す準備が整ったので、次回の記事で、説明しよう。

武蔵国橘樹郡の名刹:影向寺を訪問する

川崎市宮前区にある影向寺(ようごうじ)を訪れた。周辺の地図を示す。右上に多摩川が流れていて、影向寺の近くまでは沖積地だが、寺の周辺は丘陵地である。最寄り駅は、2㎞と離れているが、南武線武蔵新城だ。少し距離があるので、田園都市線梶が谷駅で下車し、バスを利用した。鷺沼行のバスに乗り、上野川で下車し、まっすぐ西に向かって歩き、第三京浜道路を越えたところに影向寺がある。道が細く、分かれ道が多くて分かりにくいので、携帯電話の地図に助けられながら、やっとたどり着いた。
f:id:bitterharvest:20180315194056p:plain

影向寺の近くには、武蔵国橘樹郡(たちばなぐん)の郡衙があったので、古代には郡寺であった可能性が高いとされている。

佐藤信編『古代史講義』によれば、郡衙の役割は、公的機能、財政機能、宗教・祭祀機能、文章行政機能、給食機能であると述べている。このうち、宗教・祭祀機能の一翼を担うのが、郡寺である。

橘樹郡は、534年の武蔵の乱の後、屯倉(大和王権の直轄地)になった場所とも重なるので、大和王権との結びつきは強かったのではと想像させられる。

影向寺は7世紀後半に造営された。遺跡からはその当時の瓦がたくさん出土している。また、圥射志国(むさしこく)荏原評(えばらこおり)の刻印のある平瓦が出土している。701年の大宝律令の成立により「郡」が使われるようになり、それまでは「評」が使われていたので、このことも7世紀後半の造営であることの裏付けを与える。

それでは、影向寺を訪れて見よう。お寺の正面は、大きな駐車場になっているが、空っぽであった。わずかに、一番奥にお寺の関係者らしい方が車を止めて、門の方へと向かっていた。
f:id:bitterharvest:20180315194129j:plain

門を入った正面には、本堂がある。薬師堂とも呼ばれ、元禄7年(1694年)に建立された。方5間、寄棟造、内陣・外陣で構成され、屋根は、最近の修理で、茅葺から茅葺形の銅板葺に改修されている。
f:id:bitterharvest:20180315194240j:plain

軒の木組みから、丁寧に建築されていることが分かる。
f:id:bitterharvest:20180315194338j:plain

本堂の横には、聖徳太子堂がある。8角形の建物である。
f:id:bitterharvest:20180315194400j:plain

さらに奥に入ると、阿弥陀堂がある。
f:id:bitterharvest:20180315194427j:plain

山門の前には、お寺の由来が記されている。
f:id:bitterharvest:20180315194501j:plain

近くには郡衙跡がある。
f:id:bitterharvest:20180315194521j:plain

帰りはバスを利用せず、武蔵新城駅まで歩いた。幼いころ、この近くに住んでいたが、幼稚園の横を流れていたどぶ川がきれいなせせらぎになっているのには驚かされた。
f:id:bitterharvest:20180315194814j:plain

表現可能関手

5. 表現可能関手

日本の歴史を学んでいるといくつもの学説に出会う。それらの中で、圏論との関わりが面白いと思った学説が中世史にあったので、それを最初に紹介しよう。

一つは、「権門体制論」である。黒田俊雄氏の学説で、中世の国家体制は、公家権門(執政)、宗教権門(護持)、武家権門(守護)の三つの権威から成り立っていて、それらは相互補完的な関係にあると主張されているものである。

また、佐藤進一氏は、室町時代初期の統治体制を「将軍権力の二元論」だと言っている。これは将軍権力が主従的支配権と統治的支配権に分割され、運用されているという学説で、主従的支配権はいわゆる「御恩と奉公」を基盤とした軍事、統治的支配権は「建武式目」をベースとした法による統治である。

二つの学説に対しては賛否両論があるようだが、歴史認識から離れて、数学の圏論という立場からは、構成要素を重視している「権門体制論」は「対象」という視点から、支配者と被支配者との関係に重みを置いている「将軍権力の二元論」は「射」から中世史を考察しているように見える。物事を分析するとき、対象を中心にしてみる見方と射を中心にしてみる見方の二つがあることは面白いことだと思う。

さらに、圏論に深くかかわっていると、圏の構造を表現している射の方が大切に見える。この立場から言うと、黒田氏よりも佐藤氏の見方に同調できる部分が多いのだが、どうだろうか。

そのような議論を打ち消してくれるのが、今回の表現可能関手(representable functor)だ。対象と射の間は行ったり来たりできますよと説明してくれる。

今回の話を分かりやすくするために、別の話題を挙げておこう。今日では、電卓を使えば、三角関数や常用対数の値を簡単に求めることができるが、このような道具がなかった時代には、数表が便利な道具であった(下図)。アマゾンで調べてみると、1981年には、まだ、吉田洋一・吉田正夫著『数表(新数学シリーズ)』が出版されている。おそらく、このころからそんなに離れていない時代に、書籍としては出版されなくなったのであろう。しかし、電卓が発明されていない時代には、重宝した。
f:id:bitterharvest:20180216123738j:plain

この数表が表現可能関手だ。数表は関数をテーブルという形式で表してくれる。関数の方はもちろん圏論での射である。テーブルはデータだ。圏論での対象に当たる。従って、数表は、射を対象で表したものだとは思えないだろうか。表現可能関手は、このような関係を圏論の言葉で厳密に定義したものである。そう思うと、なあんだということになるけれども、「数表」を数学的な概念に変えた意義は大きいと思う。

5.1 自然変換

圏論の中で自然変換は大切な概念である。関手から関手への関数が自然変換である。関手は、ある圏の構造を別の圏に、その構造を維持したまま移動させてくれる。そして、圏を対象とし、関手を射とすることで、抽象化された新たな圏を構成できる。これは「圏の圏」と呼ばれる。そして、二つの圏の間で関手を考えたように、二つの「圏の圏」の間で関手、この場合には自然変換と呼ばれるが、を結ぶことで、さらに抽象化が可能となる。そして、素晴らしいことに、自然変換によっても元々の圏の構造は維持される。

従って、圏論では、関手と自然変換によって抽象化を進めることができるが、その抽象化はもともと持っていた圏の構造を維持するという貴重な性質を有している。

そこで、ここではもう一度、自然変換について復習しておこう(下図)。
f:id:bitterharvest:20180215212310p:plain

今、圏\(\mathcal{C}\)が存在したとしよう。\(\mathcal{C}\)を圏\(\mathcal{D}\)に関手\(F,G\)で写したとしよう。一般に、関手によって写された空間はシート(sheet)と呼ばれる。図では、\(F\)によって写された部分を\(Sheet \ A\)とし、\(G\)による部分を\(Sheet \ B\)とした。玉ねぎは鱗片葉と呼ばれる厚肉の皮が層をなしているが、玉ねぎを圏と考えて鱗片葉をシートとすれば、イメージしやすいと思う。

自然変換はシート間で写像を結んだ時、それらが可換になるというものだ。これだけでは曖昧なのでもう少し厳密に定義することとしよう。今、\(\mathcal{C}\)の任意の対象を\(X\)としよう。そして、\(X\)をソースとしている任意の射を\(f\)とし、そのターゲットを\(Y\)としよう。即ち、\(f : X \rightarrow Y\)としよう。ここで、\(X,f\)は任意であることに注意してほしい。

自然変換は、要素ごとに可換になっていることである。そこで、\(X\)について考える。この時、\(Sheet \ B\)から\(Sheet \ A\)への射\(\alpha\)を考えることとする。これが自然変換となるためには、対象\(X\)によって作られた四角形、この場合には\(F(X),F(Y), G(X),G(Y)\)が可換である、即ち、\(F(f) \circ \alpha_X\)と\(\alpha_Y \circ G(f) \)が同値である。

即ち、\(F\)と\(G\)の間に自然変換が存在するための必要十分条件は、任意の\(X,f:X \rightarrow Y\)に対して
\begin{eqnarray}
F(f) \circ \alpha_X = \alpha_Y \circ G(f)
\end{eqnarray}
が成り立つことである。

なお、\(Sheet \ A\)から\(Sheet \ B\)への射\(\beta\)についても同様である。

5.2 \(\rm{Hom}\)関手

それでは、射を対象として扱えるようにしよう。それは次のように行う。対象を一つ定める。そして、この対象から出ている射を利用して、それにつながる対象を覗けるようにするというのが趣旨だ。具体的には、対象の一つを\(A\)としよう。そして、任意の対象を\(X\)としよう。これらの対象間の射の集合を\(\mathcal{C}(A,X)\)とする。ここで、\(\mathcal{C}\)はこれら対象が属している圏である。射の集合は\(\rm{Hom}\)集合とも呼ばれる。以後ではこの言葉を用いることとする。

\(\mathcal{C}(A,X)\)が属す圏を集合の圏となるので\(\mathbf{Set}\)と表すことにしよう。この圏では\(\mathcal{C}(A,X)\)が対象となる。即ち、\(\mathcal{C}\)では射と見なしていたものが、圏\(\mathbf{Set}\)では対象として使われる。抽象化のレベルが一つ上がったと考えてよい。\(X\)が任意であることから、圏\(\mathcal{C}\)から圏\(\mathbf{Set}\)への関手が存在する。これを\(\mathcal{C}(A,-)\)と記述し、\(\rm{Hom}\)関手と呼ぶこととする。
f:id:bitterharvest:20190527124752p:plain
上記の説明では\(A\)から出ていく射の集合を考えた。もちろん、下図のように\(A\)へ入ってくる射の集合も考えることができる。前者は共変関手、後者は反変関手と呼ばれる。双対の関係にあるが、ここでは共変関手を用いて、表現可能関手を説明する。反変関手の場合はどのようになるかは、別途、考えて欲しい。
f:id:bitterharvest:20190527124811p:plain

5.3 表現可能関手

上の説明で、関手\(\mathcal{C}(A,-)\)を用いて、圏\(\mathcal{C}\)から圏\(\mathbf{Set}\)に写した。しかし、下図に示すように別の関手\(F\)も写せる場合がある。このような場合、任意の\(X\)に対して、関手\(\mathcal{C}(A,-)\)と関手\(F\)の間で自然変換を考えることが可能になる。

このような時、自然変換\(\alpha : \mathcal{C}(A,-) \rightarrow F\)と\(\beta : F \rightarrow \mathcal{C}(A,-) \)が存在し、\(\alpha \circ \beta = id\)かつ\(\beta \circ \alpha = id\)が成り立つとき、関手\(F\)を表現可能関手と呼ぶことにする。なお、\(\alpha \circ \beta = id\)かつ\(\beta \circ \alpha = id\)が成り立つとき、自然同型(natural isomorphism)であるという。
f:id:bitterharvest:20190527130536p:plain

これは、関数と関数値(の列)が一対一に対応していることを意味している。

5.4 Haskellへの準備

それでは、Haskellでプログラムを書けるようにするための準備をしよう。

\(X\)からの任意の射を\(f\)とし、\(f:X \rightarrow Y\)とした時、下図のように可換図式を得る。
f:id:bitterharvest:20190527124720p:plain
上の図から次のことが言える。任意の\(X\)に対して、\(\mathcal{C}(A,X) \rightarrow F(X)\)であることから、
\begin{eqnarray}
\alpha_X : (A \rightarrow X) \rightarrow F(X)
\end{eqnarray}
逆に、\( F(X) \rightarrow \mathcal{C}(A,X)\)であることから、
\begin{eqnarray}
\beta_X : F(X) \rightarrow A \rightarrow X
\end{eqnarray}
と言える。なお、上の式では\((A \rightarrow X)\)とせず、カーリー化して\(A \rightarrow X\)とした。

そして、自然同型であるためには、次の条件を満たす必要がある。
\begin{eqnarray}
\alpha_X \circ \beta_X = id \\
\beta_X \circ \alpha_X = id
\end{eqnarray}

冒頭で、表現可能関手は数表だといった。例えば、角度に対してその正弦を与える関数\(sin’\)の数表は次のようになる。
f:id:bitterharvest:20180215212928p:plain

上の図から\(\alpha\)は次のようになっていることが分かる。
\begin{eqnarray}
\alpha : (Integer \rightarrow Double) \rightarrow [Double]
\end{eqnarray}
また、\(\beta\)は次のようになる。
\begin{eqnarray}
\beta : [Double] \rightarrow Integer \rightarrow Double
\end{eqnarray}

5.5 Haskellで表現可能関手を使う

これらの知見を得て、表現可能関手をHaskellで実現しよう。まず、表現可能関手は複数の異なるデータが有する共通的な性質なので、クラスとして定義しよう。ここでは\(Representable\)と呼ばれるクラスを用意しよう。表現可能関手は今までの説明では\(F\)を用いてきたので、ここでは小文字にして次のように定義しよう。

class Representable f where

次に、このクラスで用いられる演算を定義する必要がある。それは次のものである。
\begin{eqnarray}
\alpha & :& (A \rightarrow X) \rightarrow F(X) \\
\beta &:& F(X) \rightarrow A \rightarrow X
\end{eqnarray}

しかし、この演算では\(A\)という対象を必要としている。このため、これをデータ型として定義する必要がある。Haskellでは対象はデータ型として定義される。データ型は\(type\)を用いて定義できる。
そこで、\(A\)を型コンストラクタとしその型は表現可能関手\(F\)とする。但しプログラムでは小文字の\(f\)を用いて

class Representable f where
  type A f :: *

とすればよい。ここで、\(*\)は単にタイプであることを意味する。しかし、これでは\(A\)の意味がはっきりしないので、表現可能であるということをはっきりさせるために、\(A\)の代わりに\(Rep\)を用いて次のように定義しよう。

class Representable f where
  type Rep f :: *

次に\(\alpha\)の機能は表を作ることなので、その意味を持たせて\(\alpha\)を\(tabulate\)とし\(\beta\)は表から索引してくると解釈できるので、\(\beta\)を\(index\)として、関数を実現すると次のようになる。

なお、{-# LANGUAGE TypeFamilies #-}が必要である。

{-# LANGUAGE TypeFamilies #-}
class Representable f where
  type Rep f :: *
  tabulate :: (Rep f -> x) -> f x
  index :: f x -> Rep f -> x

圏論では、自然同型となるためには、
\begin{eqnarray}
\alpha \circ \beta = id \\
\beta \circ \alpha = id
\end{eqnarray}
を満たす必要であった。しかし、Haskellはデータ型を変数として利用するため、上の条件は常に満たされる。従って、自然変換の条件が満たされているかどうかに注意を払う必要はないという便利な面をHaskellは有している。

\(tabulate\)と\(index\)の意味については上述したが、重要なので、もう一度説明をし直しておこう。上のプログラムで、\(tabulate\)は表を作る関数である。これに対して、\(index\)は次のように理解できる。\(f \ x\)で表を得る。\(Rep \ f \)で表を索引するためのキーを与える。このため、しばしば\(Rep\)に変わって\(Key\)という名前を使っている例も見かける。そして、このキーで索引して得られるのが値\(x\)である。

それでは、このクラスを利用してみよう。表現可能関手としてリストを用いてみよう。ただし、このリストは空リストを含まないものとする。そこで、次のデータ型\(Stream\)を用意しよう。

data Stream a = Cons a (Stream a)

これを用いて、クラス\(Representable\)のインスタンスを用意しよう。次のようになる。

instance Representable Stream where
  type Rep Stream = Integer
  tabulate f = Cons (f 0) (tabulate (f . (+1)))
  index (Cons x xs) n = if n== 0 then x
                                 else index xs (n-1)

\(Rep \ Stream\)のデータ型は\(Integer\)としよう。ただし、0より大きい整数とする。上記で少し述べたが、ここでは\(Rep\)よりは\(Key\)のほうが分かりやすいと思う。リスト(\(Stream\))への索引に用いているキー(\(Key\))のデータ型は\(Integer\)であると考えたほうが素直である。

次に関数の部分を説明しよう。\(tabulate\)は\(Stream\)、即ち、表(リスト)を作成する。表の中に入るのは、\(f : A \rightarrow X\)とした時、\(f(A)\)である。そこで、\(f\)を引数にして、表を作成できるようにする。即ち、\(tabulate\)は関数\(f\)に対して、\([ f(0), f(1),f(2),…]\)の表を作成する。

\(index\)は、表の\(n\)番目に入っている値を取り出す。

それでは、この表現可能関手を用いてみよう。

最初の例は、\(y= 2 \times x\)の数表を求めるものである。

a = tabulate (*2) :: Stream Integer

この数表は、\(n\)番目の場所に\(2 \times n\)の値が入っている。それでは、5番目のところを求めて見よう。

Prelude> :load "Representable.hs"
[1 of 1] Compiling Main             ( Representable.hs, interpreted )
Ok, modules loaded: Main.
*Main> a = tabulate (*2) :: Stream Integer
*Main> index a 5
10

それでは、\(n\)番目の場所に\(a\)という文字が\(n\)個は言っている数表の例を考えてみよう。数表は次のようになる。

*Main> toLetter n = if n==0 then "" else "a" ++ toLetter (n-1); b = tabulate toLetter :: Stream String

やはり、5番目の値を求めて見よう。

*Main> index b 5
"aaaaa"

最後に、角度からその正弦を求める関数\(sin'\)の数表を考えよう。\(n\)番目には、\(n\)度に対する制限が入っていることにする。数表は次のようになる。

*Main> sin' :: Integer -> Double; sin' x = sin $ (fromIntegral x) * pi / 180; c = tabulate sin' :: Stream Double

それでは5度の時の正弦を求めて見よう。

*Main> index c 5
8.715574274765817e-2

上記のプログラムを図で示すと下図のようになる。
f:id:bitterharvest:20190527125040p:plain
プログラムの全体を示しておこう。

{-# LANGUAGE TypeFamilies #-}

class Representable f where
  type Rep f :: *
  tabulate :: (Rep f -> x) -> f x
  index :: f x -> Rep f -> x 

data Stream a = Cons a (Stream a)

instance Representable Stream where
  type Rep Stream = Integer
  tabulate f = Cons (f 0) (tabulate (f . (+1)))
  index (Cons x xs) n = if n== 0 then x
                                 else index xs (n-1)

a = tabulate (*2) :: Stream Integer
--index a 5

toLetter n = if n==0 then "" else "a" ++ toLetter (n-1)
b = tabulate toLetter :: Stream String
--index b 5

sin' :: Integer -> Double
sin' x = sin $ (fromIntegral x) * pi / 180
c = tabulate sin' :: Stream Double
--index c 5

成長させ過ぎた脂肪腫を除去する

お医者さんに「成長させ過ぎですね」と言われた背中のこぶを取ることになった。あの事件が起きなければ、いまだに、こぶが自己顕示欲を高めながら、息づいていたかと思うと、なくなってしまった今となっては哀れにさえ思える。

事件は突然起こった。昨年の秋は雨の多い日が続いたが、そのようなある日、冷蔵庫の中が寂しくなってきたので、近くのスーパーに買い物に出かけた。野菜や果物や肉を満載したカートを押しながら屋上の駐車場に出ようとしたときだった。運の悪いことに、駐車場への出口はスロープになっていて、表面は雨で濡れていた。

スロープに2,3歩踏み出したとき、両足のテニス靴がゆっくりと滑り始めた。体を支えようとしてカートに体重を移した瞬間に、カートが勢いよく逃げていった。あっという間の出来事なのだが、転んでいくなか、スローモーションのようにコントロールのできない体の動きを体験する。テニス靴がスピードにのって前にどんどん滑っていく。腰が後方に取り残され、お尻がまっすぐにすとんと落ちていく。一生懸命に体を支えようとした左の肘を、したたかにコンクリートに打ち付けつけてしまった。

後ろにいたおじさんに、「大丈夫ですか」と心配される。強がって、「大丈夫です」といったものの、かなり強く打ったようで、痛みは激しい。手を動かすことはできたので、たいしたことではないだろうと高をくくっていた。

このような事件があったことも忘れていた2か月後に、湯船につかって、肘をお風呂の側面に押し付けたとき、感覚がないことに気が付いた。触ってみると、肘に大きなこぶができている。柔らかいこぶで、手で押すと移動する。転んだことが原因なのだろうかと思い、定期検査の時に知り合いの医者に相談したら、整形外科で見てもらってはというアドバイスを受けた。

毎年、近くの病院でインフルエンザの予防接種を受けているが、都合のよいことに、その病院のお嫁さんが、独立して新たに整形外科の病院を開院したので、早速、そこで、診察を受けることにした。開業二日目で、ほかに患者もおらず、丁寧に時間をかけて診察していただいた。肘のこぶを見た瞬間、看護婦さんと顔を見合わせて、嬉しそうに「滑液包炎ね」と言われた。通常は、滑液包の中に少量の液体が入っていて、クッションの役割をしているのだが、転んだことで炎症を起こし、滑液が余分にたまっているということで、注射針を刺して抜いてもらった。

そして、もう一つこぶがあるので診てくださいとお願いしたところ、「二つもこぶのある人は珍しい」と言いながら、背中に目をやった瞬間、「こちらの方は大変」と言われた。5cmを超えると「生検」が必要とも言われた。とりあえず、エコーで調べてみようということになったが、大きすぎて全体が写らないとのこと。「ずいぶん大きい」と言われた。大きな病院で、「MRIを撮ってもらい、それで判断しよう」ということになった。

紹介された病院でMRI検査を受けた。MRI検査の技師の人からも「大きいですね」と言われる。そして、MRI装置のベッドにうつ伏せに寝てくださいと指示される。顔の部分にタオルを敷いてもらい、顔を横向けにする。すごい音がするということで、耳栓をする。撮影には30分程度かかったが、顔を横に向けていたため首が痛くなり、往生した。技師の人によると、「おそらく脂肪腫でしょう」ということだった。ただ、通常は、一つの袋の中に脂肪が収まっているが、いくつもの袋があるようだといって、画像を見せてくれた。綺麗に撮れるものだなあと感心しながら、CDに画像をコピーしてもらった。

整形外科医に戻って、MRIの検査結果を診断してもらった。脂肪腫ということだった。脂肪腫は、よくみられる病気の一つのようで、妻の知り合いの中には手術を受けた人が少なからずいた。文字通り、脂肪の塊が体の中にできるのだが、その原因は不明だそうだ。5cm以下の大きさなら様子を見ることになるそうだが、残念ながらその大きさをはるかに超えて、直径9㎝、厚さ3㎝であった。手術をし、さらに、生検した方がよいとのことだった。この病院では手術はできないので、大きな病院を紹介するということなので、お願いした。

年が明けて、紹介してもらった病院を訪れた。ここのお医者さんにも、見た瞬間に、「大きいですね。成長させ過ぎましたね」と言われた。そして、「ここまで大きいと、麻酔も多く使うし、大きな空間ができるので溜まった血液を抜くためのドレーンも必要になります」と言われた。手術は、1時間程度、日帰りで大丈夫ですということなので、お願いした。

手術前の検査が必要ということで、尿検査、血液検査、レントゲン検査を受けた。医療者の安全を守るために、HIV検査、肝炎検査が含まれていた。

1週間後の手術ということだったが、運悪く、風邪をひいてしまった。予定していた手術の前日に病院に相談したら、「風邪が完全に治ってから改めて手術日を決めましょう」ということになった。

再度仕切り直して、1月30日に手術することになった。手術の当日、1階の処置室で手術着に着替え、看護婦さんに案内されて、2階の手術室に向かった。手術には、診察してくれた主治医の先生のほかに、若いお医者さんが一人加わっていた。看護婦さんは2人だ。

MRI検査での嫌な記憶が残っていたため、うつ伏せに寝ることに抵抗があったが、今回は、苦しくならないように配慮されていた。胸のところに小さなウォータベッドのようなものが置いてあり、上半身を支えられるようになっていた。これで、頭を横に向けなくても済むと思って安心した。顔が当たる部分には、タオルが置かれていたが、タオルを追加してもらい、顔が快適な位置にくるように調節した。

ベッドに横たわると、左腕の血管には点滴用の針が刺され、指には脈拍数・酸素測定のための、右腕には血圧計のための器具が装着され、また左の太ももに電気メスの対極板が貼られた。

背中の部分だけを出して、他の部分は大きなシーツで覆われた。このため、シーツの中に顔が埋まってしまい、周りは全く見えない。看護婦さんが、シーツをめくり、声をかけてくれるときだけ、孤独感から解放されるという状態に置かれた。

準備の間、二人のお医者さんが相談している。「これは大きい」と言っている。こういう時のお医者さんの心境はどのようなものなのだろう。複雑な手術で嫌だなあと感じているのだろうか、それとも、普段できないことに挑戦する機会を得て、うれしいと思っているのだろうか。私なら絶対に後者だが、お医者さんもそうであってくれればと願っているうちに、「タイムアウトです。それでは始めましょう」と掛け声がかかった。手術の時の決まりなのだろう、「大きい脂肪腫の除去。氏名はXXさん。年齢はX」と言っていよいよ始まった。

局部麻酔なので、背中の数か所に麻酔が注射される。針が入っていくときの痛みはあるが、たいしたことはない。手術を受けるにあたって、MRIの画像をパソコンであらかじめ詳細に見て、素人なりに手術の手順を想定してきたので、お医者さんの会話や背中からの刺激をもとに、進行状況を想像した。

最初は、皮膚の部分を切っているのだろう。少し、焦げたような臭いがした。手術の前に、「痛みを感じたら言ってください。麻酔を足しますから。我慢して、急に飛び上がられても危険ですから、お願いします」と言われていた。切り始めて、すぐに痛みを感じ始めたので、「痛いです」と言うと、麻酔を足しますといって処置してくれた。

脂肪腫の取り出しが始まったのだろう。右上の方を切断しているようだ。何かで引っ張っているのだろうか抵抗感がある。そうこうするうちに、電気メスで切り始めたのだろうか、痛みを感じる。この痛み、何とも不思議だ。今までは感じたことがないような痛みだ。それほど強いわけではない。何か、びりびりとくるような気持のよくない痛みだ。我慢できそうなのだが、手術前に言われているので、痛いことを何度となく告げた。

この部分が終了し、他の部分を処置した後、下側の切除が始まった。同じような痛みを感じる。同じ痛さなのだが、痛みは長いこと継続していると、耐えることが苦痛になってくる。「痛い」と連発する。麻酔を足しても、痛いというので、若いお医者さんが疑問に思ったようだ。「どこら辺が痛いですか」と質問してくる。「背中の下の方です」と答えると、主治医の先生が、「複雑な部分を切除しています。全部、取りきらないと再発する恐れがあります。もう少しで終了しますので、頑張ってください」という。難しい部分だったのだろう、若いお医者さんが「先生、お見事ですね」と言う。うまくいったのだろう。主治医の先生が、「気をつけないとこの部分は穴をあけてしまうよ」と言っている。主治医の先生はきっと上手な外科医なのだろうと安心した。

他の部分の切除が始まった。軽やかに切れていくような感触が伝わってくる。安心して、静かにしていると、お医者さんの方が心配になったのだろう。「XXさん、大丈夫ですか」と確認に来る。「大丈夫です」と元気に応えて、お医者さんに不要な心配をかけないようにする。でも、緊張が続いたせいか、右側のこめかみの血管のあたりで頭痛を感じるようになる。血圧が高くなっているためかなと思って、気を静めるように心がける。あまりに静かにしていたためか、お医者さんから再度確認が入る。同じように、元気に「大丈夫です」と答える。その後も何回かは痛い時があったので、時々は痛いですと訴えた。そうこうするうちに、「もう少しで終わりです。止めてくれと言われても、もうダメです。もう少し頑張りましょう」と言われた後は、静かに終了するのを待った。

看護婦さんがシーツをあげて、「切り口をきれいに縫っていただいて、終わりになります」と教えてくれた。やっと、終わりかとほっとする。お医者さんの方は、「きれいにと注文されたので、心して綺麗に縫わなければ」と冗談を飛ばしながら、作業をしてくれた。その間、縫合に使われる糸の特徴についてお医者さんの間で意見を交わしていた。ゆるみやすい糸があるなどと言っていたが、今回はどのような糸を選んでくれたのだろうか。

最後に、脂肪腫除去後の空間に溜まる血液を排出するためのドレーンがつけられ、手術は無事、終了した。

看護婦さんの話では、電気メスによる痛みは広い範囲に及ぶので、麻酔の効いていない離れた部分で痛みを感じるのだろうと言っていた。「我慢すればよかったのですが、なかなかね」と看護婦さんに言うと、「我慢しなくてよかったのですよ」と言われた。麻酔は39cc使ったそうだ。あらかじめ40ccを用意したとのこと。この量が限度なのだろう。配分を考えながら、麻酔を打ったものと思われる。このため、痛いといったとき、麻酔が足されないことも多かったのではないかと推察した。

ドレーンは、切開場所ではなく少し離れたところに、差し込まれている。そこに細い管が繋がっていて、その先には、液を溜める容器が接続されている。血液はスポイトと同じ原理を用いて自然と抜かれる。液を溜める容器は弾力性があり、容器に取り付けられている栓を外し、容器を押してペチャンコにすると中の空気が抜ける。この状態で、栓をすると容器の中が減圧された状態となるので、ドレーンの管を通して手術跡に溜まった血液を容器に排出できる。簡単な装置なのだが、何とも、よくできている。ドレーンは4日後に取り外しましょうとなった。

お風呂は足浴ならぬ下半身浴ならばよいとのこと、シャワーは浴びてもよいとのことだったが、傷口が濡れるのが嫌だったので、シャワーの方はしばらく我慢することとした。2月10日に生検の結果が分かる。良い結果であることを期待している(検査結果はただの脂肪腫だった。一安心した)。

複雑な手術を手際よくこなし、順調に痛みもなく回復してゆく状況に接するとき、プロとしては当たり前のことなのだろうが、お医者さんはすごいなあと改めて感じて、感謝の気持ちでいっぱいになった。

自由モノイド

4.自由モノイド

圏論では、普遍性という概念はとても大切である。普遍性は、その言葉が示すように、数学の多くの分野で共通する性質を示したものである。前の記事で説明した極限と余極限も広い分野での共通の性質であるため、圏論での重要な普遍性の一つとなっている。ここでは、普遍性のさらなる例として自由モノイドを説明する。

4.1 自由モノイドの定義

加算や乗算などの二項演算子を用いて計算されるものはモノイドと呼ばれる。モノイドについても普遍性を考えることができる。それは自由モノイドと呼ばれるものだ。これを説明するために、モノイドについて、まずは、集合論での定理から始めてみよう。

1)集合論での定義

集合論でのモノイドは、ある集合\(M\)に対して、
1) 二項演算子\(\mu\)が存在し、\(\mu : M \times M \rightarrow M\)である。
2) 単位律が成り立つ。即ち、単位元\(e\)が存在し、任意の\(x \in M\)に対して\(e \ \mu \ x = x = x \ \mu \ e\)である。
3) 結合律が成り立つ。即ち、任意の\(x,y,z \in M\)に対して\( (x \ \mu \ y) \ \mu \ z = x \ \mu \ (y \ \mu \ z) \)である(わかりにくい時は、\(\mu\)を\(\times\)あるいは\(+\))などで置き換えるとよい。

モノイドの例を挙げておこう。整数での加算は、単位元を0とするモノイドである。ただし、どの整数も二つの整数を加算することで得られるが、その組み合わせは無数である。例えば、3という数は、0+3,1+2, 2+1,3+0,4+(-1),....となる。

もう一つの例は数である。例えば、346という数は、3という数に、4という数を接続し、さらに、6という数を接続することで得られる。これは、加算とは異なり、一つの数を得る方法は複数存在せず、一意であることに注意する必要がある。

そこで、モノイドを作る元(これを生成元と呼ぼう)を用意して、モノイドの定義に従って、集合\(M\)を得ることを考えよう。

今、生成元を\(a,b\)としよう。これに二項演算子\(\mu\)を適用して、モノイドの集合\(M\)を作ってみよう。\(M\)には、生成元と単位元が含まれる。そこで、\(M\)に\(e,a,b\)を含めよう。

次に、これらから\(M\)の新しい要素を作ろう。また、新しい要素は\(\mu\)を省いて表すことにする。
\begin{eqnarray}
a \ \mu \ a &=& a a \\
a \ \mu \ b &=& a b \\
b \ \mu \ a &=& b a \\
b \ \mu \ b &=& b b \\
a \ \mu \ e &=& a \\
e \ \mu \ a &=& a \\
b \ \mu \ e &=& b \\
e \ \mu \ b &=& b
\end{eqnarray}

ここまでで、\(M=\{e,a,b,aa,ab,ba,bb\}\)となる。
新しく得られた要素を利用して、さらに\(M\)の要素を作ってみよう。
\begin{eqnarray}
a a \ \mu \ a &=& a a a \\
a a \ \mu \ b &=& a a b \\
a b \ \mu \ a &=& a b a \\
a b \ \mu \ b &=& a b b \\
b a \ \mu \ a &=& b a a \\
b a \ \mu \ b &=& b a b \\
b b \ \mu \ a &=& b b a \\
b b \ \mu \ b &=& b b b \\
a a \ \mu \ e &=& a a \\
…………..
\end{eqnarray}

ここまでで、\(M=\{e,a,b,aa,ab,ba,bb,aaa,aab,aba,abb,baa,bab,bba,bbb\}\)をえる。
この操作を繰り返すことで、モノイドの要素を求めることができる。そして、このように定義だけを利用して作られたモノイドは自由モノイドと呼ばれる。

これに対して、出現した生成元の順番が異なるにもかかわらず、それらを同一なものと見直すモノイドもある。例えば、加算や乗算では、\(a \ \mu \ b= b \ \mu \ a\)と見なされる。このようなモノイドは自由モノイドとは言わない。

2)圏論での定義

圏論ではモノイドは、集合論での定義の時に用いた\(M,e,\mu\)を利用して、次のように定義される。圏論を学び始めると、モノイドは次のように定義される
1) 対象:シングルトン(星印で表されることが多い)
2) 射:集合\(M\)の要素
3) ソースとドメイン:*
4) 恒等射:単位元\(e\)
5) 結合:関数\(\mu\)
さらに、単位律、結合律が成り立つである。

さらに学習を続けると、モノイドは、抽象度をたためて、モノイド圏として次のように定義される。

1) 対象:関手の集まり\(\{M,I\}\)
2) 射:自然変換\(\mu\)
3) ドメイン・コドメイン
4) 恒等射:自然変換 \eta
5) 合成:\(\circ\)

ここで、モノイド圏の\(M\),\(\mu\)は、集合論での集合\(M\)と二項演算子\(\mu\)と同じ役割を持つ。また、モノイド圏の\(I\), \etaは、集合論での単位元\(e\)と同じ役割を持つ。ただし、モノイド圏では、\(M\)と\(I\)は対象となっている。そして、専門的な用語で説明すると、\(M\)は自己関手で、\(I\)は恒等関手である。射\(\mu\)は、\(M \times M\)から\(M\)への自然変換である。抽象度が上がっているので、特殊な用語を用いているが、二項演算子の定義と同じ役割を担っている。恒等射 \etaは、\(I\)を\(M\)に写す自然変換である。これは、単位元をモノイドの集合に組み込む仕掛けを与えていると思えばよい。

また、モノイド圏である時、上記で説明した関手と自然変換の間では次の可換図式が成り立つ。この可換図式は、\(mu:M \times M \rightarrow M\)であり、 \eta :I \rightarrow Mである。
f:id:bitterharvest:20190527115131p:plain
なお、この可換図式から、モノイド圏では単位律と結合律が満たされることが分かる。これらについて詳しく知りたいときは、モノイド圏の記事を見て欲しい。

3)Haskellとの関係

Haskellでの自由モノイドは、リストである。圏論での自由モノイドと対比させると、恒等射は[ ]、結合は++となる。対象は、数を扱っている場合であれば数のリスト、文字を扱っている場合であれば文字のリストとなる。

例えば、数のリストだとすると、次のような例を得ることができる。

Prelude> a=[]
Prelude> b = [1]++[]
Prelude> b
[1]
Prelude> c = [2]++b
Prelude> c
[2,1]
Prelude> d =[2,4,5]++c
Prelude> d
[2,4,5,2,1]
Prelude> :t d
d :: Num a => [a]

文字のリストの例も挙げておこう。

Prelude> a=[]
Prelude> b= ['a']++a
Prelude> b
"a"
Prelude> c =['n','a','r']++b
Prelude> c
"nara"
Prelude> :t c
c :: [Char]

自由モノイドではないモノイドの例としては、数での加算や乗算などを挙げることができる。
乗算のモナイドを例にとってみよう。最初の例で、恒等射[ ]を1とし、結合++を*にすると、

Prelude> a=1
Prelude> b = 1*a
Prelude> b
1
Prelude> c = 3*b
Prelude> c
3
Prelude> d = 2*5*6*c
Prelude> d
180
Prelude> :t d
d :: Num a => a

4.2 集合論をベースにしたモノイドの圏

集合論の世界の中で、モノイドを構成する要素を生成元から作り出す手順を説明したが、これを圏にすることとしよう。そして、この圏を\(\mathbf{Set}\)と呼ぶこととしよう。

このために、生成元だけで構成された集合\(X\)を用意する。次に、この生成元から自由モノイドを作り出す。ここで得られた集合を\(M\)とし、\(\mathbf{Set}\)の対象の一つとすることとしよう。なお、自由モノイドは、その生成の仕方からわかるように、一つであることが分かる。なお、この時の生成方法を射\(p:X \rightarrow M\)とする。

さらに、\(X\)を必要な生成元とすることで作られたモノイドの集合\(N,N’…\)をそれぞれ\(\mathbf{Set}\)の対象の一つとすることとしよう。この時の生成方法を射\(q,q’...:X \rightarrow N,N’\)とする。

このようにして作られるモノイドには、異なる二つの要素間での二項演算の結果が一致するような加算や乗算から作られたものが含まれるし、\(X\)にさらに元を加えて作り出されたモノイドなども含まれる。これにより下図のような圏が得られる。
f:id:bitterharvest:20190527115149p:plain

4.3 圏論をベースにしたモノイドの圏

集合論をベースにしたモノイドの圏\(\mathbf{Set}\)が作れたので、次は、圏論をベースにしたモノイドの圏\(\mathbf{Mon}\)を作ることにしよう。これは、簡単で、モノイド圏をそのまま使えばよいので、下図を得る。また、自由モノイドでの射は\(\mu\)とし、そうでないモノイドの射は\(\nu,\nu ’\)とした。
f:id:bitterharvest:20190527115208p:plain
さて、圏\(\mathbf{Mon}\)は、対象間に射を設けられる可能性がある。ここでは、とりあえず、自由モノイドとそうでないモノイドとの間に射を定義しておこう。それらを\(h,h’...\)としよう。

そして、以下の議論で、これらの射\(h,h’...\)が、一意的な準同型写像になることを示し、自由モノイドが圏論での普遍性の一つとなることを示す。

4.4 モノイド圏と集合圏を忘却関手でつなぐ

圏論での世界と集合論の世界をベースにした圏が構成できたので、この二つの圏を関手でつなぐことを考えよう。そこで、圏\(\mathbf{Mon}\)から圏\(\mathbf{Set}\)への関手\(U:\mathbf{Mon} \rightarrow \mathbf{Set}\)を用意しよう(下図)。この関手は、\(\mathbf{Mon }\)の対象を\(\mathbf{Set}\)の対象に写し、射については何も行わない。この関手\(U\)は忘却関手(forgettable functor)と呼ばれる。これは、圏論の世界でモノイドが有していた構造が、集合論の世界に写したときに失われることによる。
f:id:bitterharvest:20190527115229p:plain
忘却関手を用意したので、集合論の世界での対象\(M\)は対象\(N\)に\(U(h)\)で写される。

4.5 モノイド圏と集合圏を忘却関手でつなぐ

今までの説明だけではわかりにくいので、例を用いて具体的に示そう。二進数\(0,1\)の場合を考えてみよう。自由モノイドの方を二進数の数とし、そうでないモノイドの方を2進数の乗算とすると下図を得る。この図で、\(p\)は2進数の数を、\(q\)は2進数の乗算の結果を作り出す関数である。なお、任意の\(a,b \in \{0,1\}\)に対して、\(h([a]++[b])=h(a)*h(b)\)とする。この時、準同型写像になっていることを確認して欲しい。
f:id:bitterharvest:20190527115247p:plain
次の例は、生成元を増やしてモノイドを作る例だ。ここでは、右側のモノイドを生成するときに、生成元に2を加えて、3進数の数を作り出すことを考えよう。この場合には、下図をえる。もちろん、\(q\)は3進数の乗算の結果を作り出す関数である。この時も、任意の\(a,b \in \{0,1\}\)に対して、\(h([a]++[b])=h(a)*h(b)\)とする。そして、準同型写像になっていることを確認して欲しい。
f:id:bitterharvest:20190527115306p:plain
生成元を増やしたので、次は、いくつかの生成元は同じだと見なしてモノイドを作る例を考えよう。次の例は、いずれの生成元に対しても、集合\(N\)の同じ要素に写像されるようにしよう。以下の例では、1に写されるようにした。そして、下図のようになる。同じように、任意の\(a,b \in \{0,1\}\)に対して、\(h([a]++[b])=h(a)*h(b)\)とする。これも、準同型写像になっていることを確認して欲しい。
f:id:bitterharvest:20190527115333p:plain

4.6 普遍性としての自由モノイド

自由モノイドを理解してもらうために、自由モノイドを集合論に基づいて定義し、その性質について説明してきた。

しかし、我々が議論しているのは、集合論の世界ではなくて圏論の世界である。このため、自由モノイドを圏論の世界の中で定義する必要がある。そこで、下図の可換図式で示されるように、次のように定義する。
f:id:bitterharvest:20190527115345p:plain
モノイド圏を対象とした圏\(\mathbf{Mon}\)と集合からなる圏\(\mathbf{Set}\)が存在したとする。忘却関手\(U\)は、\(\mathbf{Mon}\)の対象となっているモノイド圏のそれぞれに対して、その対象を\(\mathbf{Set}\)の対象に写すとする。

今、あるモノイド圏\(\mathcal{M}\)から任意のモノイド圏\(\mathcal{N}\)に対して準同型写像\(h\)があったとする。また、これらの圏\(\mathcal{M,N}\)は忘却関手\(U\)で\(\mathbf{Set}\)に写されるので、これらの対象を\(\{M,I\},\{N,I\}\)とすれば、写される先は\(U(\{M,I\}),U(\{N,I\})\)となる。

さらに、生成元{X}を対象とする圏を考えよう。この圏から、先に説明した方法により、モノイドを生成することができる。このため、\(U(\{M,I\}),U(\{N,I\})\)への射が存在するはずである。それらを\(p\),\(q\)としよう。

この時、\(p \circ U(h) = q\)とするような\(h\)が一意的に定まるとき、\(\mathcal{M}\)を自由モノイドと呼ぶ。但し\(U(h):U(\{M,I\})\rightarrow U(\{N,I\})\)である。

自由モノイドが、任意のモノイド圏\(\mathcal{N}\)に対して、準同型写像\(h\)が一意的に定まるといっているので、これは圏論での重要な概念である普遍性であるということができる。

前後が逆になるが、例で挙げたものが自由モノイドになっていることを改めて確認して欲しい。

とろっと柔らかめなスクランブルエッグ

朝の食卓の用意は忙しい。パンを焼いている5分間の間に、コーヒーを沸かし、1~2品のおかずを調理しなければならない。順序だてて、手際のよい処理が必要だ。

このような要求に応えてくれるのは、スクランブルエッグだ。手間暇をかけないでも、楽しんでもらえるメインディッシュとなる。柔らかめにするか、硬めにするかは好みの問題だが、相方はとろっと柔らかめに仕上がったスクランブルエッグを好む。

そこで、今回紹介するのは、柔らかなスクランブルエッグだ。

まず、材料に登場してもらおう。
f:id:bitterharvest:20180128083634j:plain

いたって簡単。二人用ということで、卵を二つ用意する。その他に、牛乳、バター(マーガリン)、塩だ。

卵を割って、小さな椀に入れる。
f:id:bitterharvest:20180128083928j:plain

攪拌して、黄身と白身をよく混ぜる。
f:id:bitterharvest:20180128084050j:plain

牛乳50ccと塩小さじ1/4を混ぜて、さらに攪拌する。
f:id:bitterharvest:20180128084250j:plain

この段階で、パンを焼き始め、コーヒーを沸かすとよいだろう。好みでサラダあるいはソーセージを用意しておくと食卓は豊かになる。

フライパンを中火で温めて、バター10~15gを入れる。
f:id:bitterharvest:20180128084456j:plain

バターがほとんど解けたら、卵を入れる。
f:id:bitterharvest:20180128084542j:plain
f:id:bitterharvest:20180128084650j:plain

フライパンの中央部で、卵が固まり始めてきたら、この部分をへらで脇に寄せる。
f:id:bitterharvest:20180128084818j:plain
f:id:bitterharvest:20180128084849j:plain

この動作を続ける。
f:id:bitterharvest:20180128084922j:plain

全てが脇によったら、出来上がりだ。
f:id:bitterharvest:20180128085028j:plain

皿に盛り、丁度一緒に出来上がった、トースト、コーヒーなどとともに食卓を飾る。
f:id:bitterharvest:20180129074909j:plain
f:id:bitterharvest:20180129074934j:plain

朝の忙しい時間に、5分程度で食事を用意することができ、味も素晴らしいので、我が家では、好んで食べている料理である。

極限-Readerと極限

3.3 \(Reader\)と極限

ここまでの記事で、\(Reader\)の使い方と定義について詳しく説明した。この記事では、さらに理解を進めるために、圏論での極限とのかかわりについて説明しよう。圏論は、様々な数学の分野で共通に成り立っている性質について論じる学問ともみなすことができるが、極限はその一つと言える。いわゆる積と呼ばれる概念が成り立つとき、そこには極限が存在するというのが、普遍に成り立つ性質である。このような性質は普遍性(universal property)と呼ばれ、数学での重要な概念である。

1)\(Reader\)と極限

ここでは、関手\(Reader\)にも、積という概念が成り立ち、そのため、極限と呼ばれる普遍性が成り立つことを説明する。

これまで説明してきたように、関手\(Reader\)は、ある圏の対象としての射\(r: U \rightarrow X\)を、別の圏の対象である射\(Reader \ r\)へと写すものである。また、モナドとしての性質も有している。

少し前の記事で錐について論じた。そこでは、インデックス圏\(\mathcal{I}\)から圏\(\mathcal{C}\)への関手を利用して、錐を作成した。それに倣って、ここでも、圏\(\mathcal{I}\)に二つの対象\(I,J\)を用意することとしよう。また、別の圏\(\mathcal{C}\)は、先ほど説明した対象\(Reader \ r\)で構成されているとしよう。そして、インデックス圏の対象\(I,J\)を\(\mathcal{C}\)へ関手により写すことにしよう。

関手\(D\)によって、それぞれを\(Raeder \ r_I\)と\(Raeder \ r_J\)に写す。ここで、\(r_I:U \rightarrow X_I\), \(r_J:U \rightarrow X_J\)である。

また、関手\(\Delta_C\)によって、それぞれを同じ\(Raeder \ s\)に写す。ここで、\(s:U \rightarrow Y\)である。ただし、\(Y\)は、\(X_I\)と\(X_Y\)のデカルト積とする。即ち、 \(Y= (X_I, X_J)\)である。これより次を得る。

\begin{eqnarray}
&& Reader \ s \\
&=& Reader \ $ \ U \rightarrow Y \\
&=& Reader \ $ \ U \ \rightarrow (X_I, X_J) \\
&=& Reader \ $ \ U \rightarrow (X_I \times X_J) \\
&=& Reader \ $ \ (U \rightarrow X_I) \times (U \rightarrow X_J) \\
&=& (Reader \ $ \ U \rightarrow X_I) \times (Reader \ $ \ U \rightarrow X_J) \\
&=& Reader \ r_I \times Reader \ r_J
\end{eqnarray}
これより、\(Reader \ s\)から\(Reader \ r_I \),\(Reader \ r_J\)に対して射が存在することが分かる(それぞれ第一要素、第二要素を出力とするものである)。

上記の関係を可換図式で示すと下図のようになる。
f:id:bitterharvest:20180120102045p:plain

これより、\(Reader r_I\)と\(Reader r_J\)には積\(Reader r_I \times Reader r_J\)が存在することが分かる。従って、錐である。

このため、下図に示すように、任意の\(Reader \ s’\)について、一意的に定まる射が存在するような極限\( Reader \ s\)が存在する。なお、一意的に定まる射は、\(s:U \rightarrow Y\), \(s’:U’ \rightarrow Y\),\(f:U \rightarrow U’\)とした時、\(contramap f\)である。これを可換図式で示すと下図のようになる。
f:id:bitterharvest:20180120102106p:plain

2)\(Reader\)の積とHaskellとの関係

それでは、いま説明した\(Reader\)の積はHaskellではどのような時に利用されるのだろうか。それを具体的な例でみてみよう。

いま「誰ですかと問われた」ときに、「トム」と答える関数と「ジェリー」と答える関数を用意したとしよう。

tom :: Reader String String
tom = do
    env <- ask -- gives you the environment which in this case is a String
    return (env ++ " This is Tom.")

jerry :: Reader String String
jerry = do
  env <- ask
  return (env ++ " This is Jerry.")

それでは、二つの関数の積を考えることとしよう。これは、二つを実行し、その結果を一緒にして出力するような関数を用意したらどうであろうか。

tomAndJerry :: Reader String String
tomAndJerry = do
    t <- tom
    j <- jerry
    return (t ++ "\n" ++ j)

さっそく実行してみよう。

*Main> runReader tomAndJerry "Who is this?"
"Who is this? This is Tom.\nWho is this? This is Jerry."

積としての\(Reader\)を実感できたことと思う。

3)余極限との関係

\(Reader\)と極限の関係を示したので、その双対関係にあるものも存在するはずである。そこで、\(Reader\)の矢印の向きを変えた射\(Op\)を次のように定義する。
\begin{eqnarray}
Op \ $ \ r^{op} \\
r : U \rightarrow X
\end{eqnarray}
なお、上記の定義で、射\( r^{op}\)は、射\(r : U \rightarrow X \)の射の矢印の方向を反対にしたものである。

この定義に従うと、\(Op\)の可換図式は下図のようになる。
f:id:bitterharvest:20180121095941p:plain
上図で圏\(\mathcal{I}\)はインデックス圏で、\(I,J\)はそこでのふたつの対象である。この対象は、関手\(D\)によって、ある圏\(\mathcal{C}\)の対象となっている射\(Op \ r_I^{op}, Op \ r_J^{op} \)に写される。また、関手\(\Delta_C\)によって、対象となっている一つの射\(Op \ s^{op} \)に写される。

そして、\(Op \ s^{op}\)は次のように変換できる(ここで、\(s: U \rightarrow Y\)である。また、\(\sqcup\)は直和である)。
\begin{eqnarray}
&& Op \ s^{op} \\
&=& Op \ $ \ Y \rightarrow U \\
&=& Op \ $ \ (X_I \sqcup X_J) \rightarrow U \\
&=& Op \ $ \ (X_I \rightarrow U) \sqcup (X_J \rightarrow U) \\
&=& Op \ r_I^{op} \sqcup Op \ r_J^{op}\\
\end{eqnarray}

上図から、対象\(D_I=Op \ r_I^{op}\)と対象\(D_J=Op \ r_J^{op}\)から対象\(C=Op \ s^{op}\)への射が存在することは明らかである。従って、\(Op\)は余錐である。

これにより、\(Op\)は余極限を有するので、次の可換図式を得る。
f:id:bitterharvest:20180121101716p:plain

このように、\(Reader\)とその双対である\(Op\)は、圏論での積と余積であり、それぞれ極限と余極限を有することが分かる。

なお、Haskellでは、\(Op\)を\(MonadReader\)と呼ばれるモナドとして定義されている。そして、\(ask\)という関数が、環境変数の値を呼び出すために用意されている。

極限-HaskellでのReaderを定義する

3.2 \(Reader\)を定義する

Haskellでは\(Reader\)を用意している。Control.Monad.Readerというモジュールを読み込めば、使えるようになっている。しかし、このモジュールを理解しようとすると、忍耐力を必要とする。汎用性を高めるために、\(Reader\)が、\(ReaderT\)と呼ばれるデータ型を利用して、定義されているためだ。このため、\(Reader\)を理解しようとすると、別のいくつかのことを理解しなければならない。この作業は、煩わしく、下手をすると、何を理解しようとしていたのかさえ忘れてしまう。

そこで、ここでは、\(Reader\)の理解を助けるために、Haskellでの定義から離れて、必要な事項だけで説明することとしよう。

前回の記事で、\(Reader\)は

type Reader u a 

と理解して、利用した。これは、\(u\)という環境を与えて、\(a\)を出力するというものであった。\(Reader\)は、入力を\(u\)とし\(a\)を出力する関数を、データ型としたものと考えてよい(圏論では、データ型は対象である。関数を対象に選べるのも、圏論の汎用性、有用性を示す証左である)。そこで、次のように定義しよう。

data Reader u a = Reader (u -> a)

Haskellで用いられている用語の説明をしておこう。等式の左側に存在する\(Reader\)は型コンストラクタ(type constructor)と呼ばれる。また、\(u\)と\(a\)は型引数(type argument)と呼ばれる。\(data Reader \ u \ a\)により、データ型\(Reader\)は、データ型\(u\),\(a\)を用いて定義されていることを示す。

等式の右側の\(Reader\)は値コンストラクタ(value constructor)あるいはデータ・コンストラクタ(data constructor)と呼ばれる。これに続く\( \ u \rightarrow \ a\)は型と呼ばれる。そして、関数となっているので、特に、代数的データ型と呼ばれる。

上の定義を用いて、データ型\(Reader\)の値をいくつか求めて見よう。

Prelude> data Reader r a = Reader (u -> a)
Prelude> a = Reader $ \u -> 0.5 * u -- 数値を入力し、その半分を求める関数。もちろんa = Reader (*0.5)と入力しても同じ
Prelude> :t a
a :: Fractional a => Reader a a
Prelude> b = Reader $ \u -> show u – 数値などを入力し、その文字列を求める関数
Prelude> :t b
b :: Show a => Reader a String
Prelude> c = Reader $ \u -> show (u* u) – 数値を入力し、その2乗の文字列を求める関数
Prelude> :t c
c :: (Show a, Num a) => Reader a String
*Main> d :: Reader Int String ; d= Reader $ \u -> show ((*2) u) – 正数を入力し、その2倍の文字列を求める関数。これはd = Reader $ show . (*2)と同じ
*Main> :t d
d :: Reader Int String

1)ファンクタとしての定義

データ型が用意できたので、このデータ型を用いる関数を用意しよう。ファンクタから始めよう。

関手\(Reader\)は、与えられた関数を\(Reader\)というコンテナで包むものである。

データ型が用意できたので、このデータ型を用いる関数を用意しよう。ファンクタから始めよう。ファンクタでは、\(fmap \ f\)という関数を用意する必要がある。

圏\(\mathcal{C}\)は、関手\(Reader \)によって圏\(\mathcal{C’}\)に移されるものとしよう。そして、圏\(\mathcal{C}\)は、射の集まりで構成され、それらは\(U\)から\(X\)への射(\(r:U \rightarrow X\))としよう。また、任意の射\(r\)は、関手\(Reader\)によって圏\(\mathcal{C’}\)の射にコンテナをかぶせられた射\(Reader \ r\)に写されるものとしよう。

なお、コンテナをかぶせられた射は、関数\(runReader \ (Reader \ r) \ u\)を実行することにより、\(r(u)\)の値を出力する。
これらの関係を示すと下図になる。
f:id:bitterharvest:20180114104956p:plain
次に、圏\(\mathcal{C}\)内に別の射\(f\)が存在し、これは、射\(r\)を別の射\(s\)に写すものとする。このとき、\(s = f . r\)となる。また、\(f\)を関手\(Reader\)で写したものを\(fmap \ f\)としよう。これらから、次のような可換図式を得る。
f:id:bitterharvest:20180114105557p:plain
プログラムで示した例\(r =(*2),f=show\)の可換図式を求めると下図のようになる。
f:id:bitterharvest:20180114111313p:plain
それでは、Haskellでのファンクタの定義を示そう。

instance Functor (Reader r) where
  fmap f (Reader r) = Reader $ f.r

なお、ファンクタとして定義するとき、\(Reader\)が関手なので、\(r\)をつけずに、

instance Functor Reader where
  fmap f (Reader r) = Reader $ f.r

と定義しそうになるが、\(fmap\)の定義において、\(Reader\)ではなく\(Reader \ r\)を用いる必要があるので、\(r\)をつけないと誤りとなる。

2)モナドとしての定義

次はモナドでの関数を定義しよう。本論に入る前に、モナドがどのような数学的な概念であったかを復習しておこう。モナドとは、ある圏\(\mathcal{C}\)のモナド\(M\)とは、\(\mathcal{C}\)の対象\(A\)をコンテナ\(M\)で包んだものであり、それは\(M \ A\)と記述される。モナドには2つの関数が用意されていて、一つはモナドを作り出すもの、他の一つは、モナドからモナドを作り出すものである。二つの関数の用意の仕方にはいろいろあるが、Haskellでは\(return\)と\((>>=)\)を用意することになっている。それでは、下図を用いながら説明しよう。
f:id:bitterharvest:20180114111628p:plain

\(return\)は圏\(\mathcal{C}\)の対象\(A\)をモナドとしてのコンテナで包むことである。これを、\(return\)の型シグネチャで確認しよう。

:t return
return :: Monad m => a -> m a

例をいくつか挙げておこう。[ ] はモナドなので、整数から各括弧の付いたモナドを作成してみよう。

Prelude> a = return 3 :: [Int]
Prelude> a
[3]
Prelude> :t a
a :: [Int]

\(Maybe\)もモナドなので、整数から\(Maybe\)のモナドを作成してみよう。

Prelude> b = return 3 :: Maybe Int
Prelude> b
Just 3
Prelude> :t b
b :: Maybe Int

次の関数は、\((>>=)\)である。これは、二つの引数(\(M \ A,F\)))が与えられる。一つはモナドの値である。もう一つは関数である。この関数\(F\)は少し変わっていて、モナドにする前の値、即ちコンテナに包まれる前の値\(A\)を用いて、これをモナド\(M B\)に変換する。式で表すと、\(F: A \rightarrow M \ B\)である。型シグネチャで確認しておこう。

Prelude> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b

それでは、いくつかの例を見ていこう。まず各括弧の場合だ。\(f\)の関数は、整数が与えられたならば、それに4を加えて各括弧でくくることにしよう。次のようになる。

Prelude> a = return 3 :: [Int]
Prelude> f = \r -> [r + 4]
Prelude> c = a >>= f  -- 中置関数として
Prelude> c
[7]
Prelude> d = (>>=) a f  -- 前置関数として
Prelude> d
[7]

次は\(maybe\)の例を示そう。整数が与えられた時、それを2倍にし、モナド\(Maybe\)にして返す関数を考えよう。

Prelude> a = return 3 :: Maybe Int
Prelude> f = \x -> Just $ (*2) x 
Prelude> (>>=) a f
Just 6

モナドに慣れてきたので、\(Reader\)に対しても、\(return\)と\((>>=)\)を定義することとしよう。

まず、\(return\)から始めよう。次のように定義されている。

return x = Reader $ \u -> x

上の定義では、\(u\)の値に関わらず、\(x\)の値が定まるので、\(\backslash u -> x\)は定数関数となる。

いくつかの例を挙げてみよう。 \(return\)で出力値を与えて、\(runReader\)で実行するだけの簡単なものだ。

*Main> a = return 3 :: Reader Int Int
*Main> :t a
a :: Reader Int Int
*Main> runReader a 6
3
*Main> b = return 3 :: Reader String Int
*Main> :t b
b :: Reader String Int
*Main> runReader b "Three"
3
*Main> c = return "Jack" :: Reader String String
*Main> :t c
c :: Reader String String
*Main> runReader c "Betty"
"Jack"
*Main> 

次は、\(>>=\)である。モナドでの定義は次のようになっている。

*Main> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b

これをReaderで置き換えると次のようになる。

*Main> :t (>>=)
(>>=) :: Reader r -> return . s -> reader g

それでは、下図を参考にしながら、(>>=)を作成していこう。
f:id:bitterharvest:20180115092811p:plain

\( ( >>=)\)は基本的には、二つの関数の合成である。上の図であれば、\(Reader \ r\)と\(Reader \ s\)との合成である。\(Reader \ r\)を実行して、その出力を\(Reader \ s\)の入力として渡せばよい。入力となる値は、\(runReader (Reader \ r) \ u\)である。しかし、\(Reader \ s\)はこれを受け取れるようにはなっていないので、可能な形に変換する必要がある。それには、受け取った入力を\(s\)で変換し、それを戻す定数関数を作ればよい。これをしてくれるのは、先ほど示した\(return\)である。そこで、\(return . s\)とすればよい。従って、\(>>=\)への入力は、\(Reader \ r\)と\(f=return . s\)となる。

\(>>=\)からの出力は、\(Reader \ g\)である。そして、\(g\)は、入力を\(\backslash u\)とし、出力を\(runReader (f \ (runReader (Reader \ r) \ u)) \ u \)とする関数である。

従って、\(>>=\)は、\(Reader \ r\)を\(x\)で置き換えると次のように定めることができる。

x >>= f  = Reader $ \u -> runReader (f (runReader x u)) u

少し、用いてみよう。\(r=(*2),s=show\)の場合には、次のようになる。

*Main> a = Reader (*2) >>= return . show
*Main> runReader a 5
"10"

\(r=show,s=length\)の場合には、次のようになる。これは、入力した数字の桁数を求める関数である。

*Main> b = Reader show >>= return . length
*Main> runReader b 2018
4

最近のHaskellでは、モナドの上位クラスにアプリカティブが用意されている。従って、\(Reader\)と定義するためには、これも必要となる。ここでは、説明を省いて、アプリカティブを含めて、\(Reader\)に関連する定義を示そう。

import Control.Monad
import Control.Applicative

data Reader u a = Reader (u -> a)

instance Functor (Reader r) where
  fmap f (Reader r) = Reader $ f . r     

instance Applicative (Reader r) where
  pure x          = Reader $ \u -> x
  (Reader f) <*> (Reader x) = Reader $ \u -> (f u) (x u)

instance Monad (Reader r) where
 return x = Reader $ \u -> x
 x >>= f  = Reader $ \u -> runReader (f (runReader x u)) u

runReader :: Reader u a -> u -> a
runReader (Reader f) u = f u

ask :: Reader a a
ask = Reader $ \u -> u

メジナの香草焼き

お正月の料理にも飽きた正月8日に、例によって近くの超安売りスーパーに出かけた。我が家は、東京都に属しているのだが、遠くないところにある境川を越えると神奈川県になる。隣県側は製造関連の企業が多いことで知られていた地域だ。かつては、軒並み大きな工場が展開していたが、それらの跡地は大型のスーパーに代わっている。それでも、まだ、いくつかの大企業が残っていて、そこでは外国人の労働者がたくさん働いている。

スーパーで見かける彼らの言葉を聞いていると、南米の出身者だと分かる。中には、日本人と変わらない顔立ちの人たちも見かける。彼らの食習慣に合わせて、この大型スーパーでは、他では見かけないような大切りの肉が売られている。我々も、時々、とても大きなステーキ用の牛肉や、厚みのある豚ステーキ用の肉を購入している。

魚売り場にも、近海で取れたおいしそうな魚が、種類はそれほど多くはないが、時々売られている。この日は、いつもより多く、5種類を超えるほどの魚があった。その中に、体形の美しいメジナを見つけた。しかも、うれしいことに朝捕りで、とても新鮮だ。お刺身がおすすめだろうが、お正月料理が続いたので和食は食傷気味になっていた。もったいないような気もしたが、この日の夕飯を香草焼きで正月気分を一掃しようと考えた。

今回紹介するのは、その時のメジナの香草焼きだ。いつもの例に習って、材料たちに登場してもらおう。
f:id:bitterharvest:20180111095531j:plain

とてもシンプルだ。主材料は、もちろんメジナだ。背中からお腹にかけて幅のある魚だ。ウィキペディアで調べたら、スズキ目メジナ科となっていた。タイはスズキ目タイ科なので、お互いに親戚同士なのだろう。釣りをしないのでよく知らないのだが、釣人にとっては、人気の魚だそうで、「磯釣りの王様」と呼ばれている。

副材料は、魚の生臭さを消す役割も担わせて、玉ねぎ、トマト、ニンニクだ。その他に、ローズマリー、ハーブ入り岩塩、オリーブオイルだ。

メジナは鱗と内臓を取り除く。鱗はタイのそれと比較するととても柔らかい。タイの鱗を撮るときは大根を利用するが、メジナの場合には柔らかすぎて無理かもしれないので、包丁の刃先をなでるようにして、取り除いた。また、内臓からは肉のような塊が出てきた。食べた小魚が胃の中で消化の途中だったのだろう。
f:id:bitterharvest:20180111095600j:plain

玉ねぎ(1個)、トマト(1個)、ニンニク(2かけ)を細切りにする。
f:id:bitterharvest:20180111095637j:plain

次の作業は、岩塩の刷り込みだ。
f:id:bitterharvest:20180111095708j:plain

たまたま、旅行のお土産にもらったイタリア産のものがあったので、これを用いた。耐熱皿にメジナをのせ、まず片面にハーブ入り岩塩を小さじ半分程度刷り込み、ひっくり返して同じことをし、最後に、お腹にも刷り込む。

次の作業は野菜をのせることだ。最初に、お腹に、玉ねぎ、トマト、にんにくを入れる。
f:id:bitterharvest:20180111095754j:plain

野菜の残りを魚の上にのせる。
f:id:bitterharvest:20180111095824j:plain

200度にしたオーブンで30分ほど焼く。
f:id:bitterharvest:20180111095912j:plain

そして、食卓に供する。
f:id:bitterharvest:20180111095933j:plain

この日は、コーンスープも作った。美味しい西洋料理を楽しむことができ、正月気分から抜け出すことに成功した。

極限-HaskellでのReaderについて

3.Haskellでの極限と余極限

これまでの記事で、極限と余極限の説明をしてきた。数学的な記述が主で、Haskellを学ぼうとしている人は、役に立たないなと感じたことだろう。圏論での積や余積は、乗算や加算、あるいは、論理積論理和と関係があることは、直感でもわかる。しかし、これらのために、わざわざ圏論まで持ち出す必要はないと思われただろう。

そこで、今回は、日常的に使われるることはないが、Haskellの奥深さだけではなく、両者の繋がりを巧みに伝えてくれるHaskellの秘密兵器を紹介しよう。

3.1 \(Reader\)を理解しよう

今回、紹介する話題は\(Reader\)である。これまでも説明してきたので、理解されている方も多いと思うが、初めてという方もいることと思う。

\(Reader\)は、なかなか理解しにくいので、手始めにどのような機能を有していて、どのように利用されているかについて説明しよう。

Haskellでは\(Reader\)は次のように定義されている。

type Reader u = ReaderT * u Identity

この定義だと、\(ReaderT\)を理解することが必要になるので、手っ取り早く\(Reader\)を理解することはできない。そこで、上記の定義をとりあえず置いておき、次のように理解しておこう。

type Reader u a

ここで、\(u\)は環境と呼ばれるもので、\(a\)は環境から作り出される値である。少し、利用してみよう。

\(Reader\)には、\(return\)という関数が用意されているので、これを用いて\(Reader\)の値を作り出してみよう。

Prelude> import Control.Monad.Reader
Prelude Control.Monad.Reader> a = return 3.5 :: Reader String Double

それでは、\(return\)によって戻された値の型を確認しておこう。

Prelude Control.Monad.Reader> :t a
a :: Reader String Double

他にもいくつか試してみよう。

Prelude Control.Monad.Reader> b = return "A Happy New Year." :: Reader String String
Prelude Control.Monad.Reader> :t b
b :: Reader String String
Prelude Control.Monad.Reader> c = return 3 :: Reader Int Int 
Prelude Control.Monad.Reader> :t c
c :: Reader Int Int

\(Reader\)には、\(runReader\)という関数も用意されている。この関数は、\(Reader\)の値と、環境の値とを入力すると、\(Reader\)の2番目の型変数に対応した値を出力してくれる。まずは実行例を示そう。

Prelude Control.Monad.Reader> a = return 3.5 :: Reader String Double
Prelude Control.Monad.Reader> d = runReader a "Please, execute it."
Prelude Control.Monad.Reader> d
3.5
Prelude Control.Monad.Reader> :t d
d :: Double

当然、環境の値のデータ型が異なると実行されない。

Prelude Control.Monad.Reader> runReader a 3
<interactive>:22:13: error:
    ? No instance for (Num String) arising from the literal ‘3’
    ? In the second argument of ‘runReader’, namely ‘3’
      In the expression: runReader a 3
      In an equation for ‘it’: it = runReader a 3

\(runReader\)の型シグネチャは次のようになっている。

runReader :: Reader u a -> u -> a

1) 預金残高と利息

\(Reader\)の性格が分かってきたところで、一般的な使い方を示そう。これまで説明しなかったが、\(Reader\)はモナドである。従って、通常のプログラミング言語と同じように、逐次的に命令の列を用意することが可能である。多く用いられているのは、環境を入力して、それに基づいて出力するというものである。

環境を入力する関数として\(ask\)が用意されている。これを利用していくつかの例を示そう。最初の例は、年利率で2%、10万円の定期預金をした時に、\(r\)年後の残高を求めることにしよう。この例では、\(r\)が環境であり、残高が出力である。

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

import Control.Monad.Reader

amount :: Reader Int Double
amount = do
  year <- ask -- 何年間預金したかを入力する
  return (100000 * 1.02 ** (fromIntegral year)) --10万円預金したときの残高を出力

このプログラムを実行してみる。3年後の残高を求めてみよう。

Prelude> :load "amount.hs"
[1 of 1] Compiling Main             ( amount.hs, interpreted )
Ok, modules loaded: Main.
*Main> runReader amount 3
106120.79999999999

3年後には、6千円以上の利息が付いていることが分かる。今の銀行預金の利率もこのくらいならば、預金しがいもあるというものだ。

それでは、利率が3%になった時の比較をしてみよう。プログラムは次のようになる。

import Control.Monad.Reader

amount2 :: Reader Int Double
amount2 = do
  year <- ask -- 何年間預金したかを入力する
  return (100000 * 1.02 ** (fromIntegral year)) --2%の年率で10万円預金したときの残高を出力

amount3 :: Reader Int Double
amount3 = do
  year <- ask -- 何年間預金したかを入力する
  return (100000 * 1.03 ** (fromIntegral year)) --3%の年率で10万円預金したときの残高を出力

amount2vsAmount3 :: Reader Int String
amount2vsAmount3 = do
  a2 <- amount2                                 --2%の年率を実行
  a3 <- amount3                                 --3%の年率を実行
  return ( "2%: " ++ show a2 ++ " vs " ++ "3%: " ++ show a3)

上のプログラムに見るように、比較のプログラム\(amount2vsAmout3\)は、2%と3%のプログラム\(amoun2\)と\(amount3\)を実行させるだけだ。

さて、これを用いて4年間預けたときでの比較をしてみよう。

runReader amount2vsAmount3 4
"2%: 108243.216 vs 3%: 112550.881"

3%にはさらに魅力を感じることだろう。4年間預けておけば、利息だけで小旅行ができそうだ。

2)米国からドイツへ移動したときの単位の変換

\(Reader\)の使い方に慣れてきたが、さらに、ダメ押しでもう一つ例を上げてみよう。今度は、米国からドイツへ移動したときの単位の変換だ。

まず、変換のためのデータ型\(Conv\)を新たに用意しておこう。次のようにした。

data Conv = Conv

次にマイルからキロメートルへの変換のプログラムを作ろう。1マイルは1.6㎞なので次のようにする。

m2k :: Reader Conv String
m2k = do
  env <- ask
  return "One mile is equal to 1.6 km."

同様にポンドからキログラムへの変換プログラムを作ろう。1ポンドは0.45kgであるので、次のようになる。

p2k :: Reader Conv String
p2k = do
  env <- ask
  return "One pound is equal to 0.45 kg."

さらに通貨を変換してくれるプログラムを作ろう。1ドルは0.83ユーロとすると、次のようになる。

d2e :: Reader Conv String
d2e = do
  env <- ask
  return "One Dollar is equal to 0.83EUR."

最後にすべての変換を与えてくれるプログラムを作ろう。これは上記のプログラムを用いて次のようになる。

u2g :: Reader Conv String
u2g = do
  length <- m2k
  weight <- p2k
  money <- d2e
  return (length ++ " \n " ++ weight ++ " \n " ++ money)

それではこれを実行してみよう。

Prelude> :load "Reader.hs"
[1 of 1] Compiling Main             ( Reader.hs, interpreted )
Ok, modules loaded: Main.
*Main> runReader u2g Conv
"One mile is equal to 1.6 km. \n One pound is equal to 0.45 kg. \n One Dollar is equal to 0.83EUR."

このプログラムは、\(u2g\)というファイルを読みだしているようには見えないだろうか。このように、\(Reader\)には、データベースやファイルを読みだしているように感じさせる機能がある。

次回は、\(Reader\)を定義して、さらに理解を深めよう。