bitterharvest’s diary

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

三河国分寺・尼寺と豊川稲荷を訪ねる

秋分の日(23日)は、三河国分寺・尼寺を訪ねた。741年に聖武天皇が全国の国々に国分寺国分尼寺を建立するようにとの詔を発した。三河の国は、この当時の中心的な地域であったのだろう、豊川の地に二つの寺が建立された。

三河国分寺・尼寺は、豊川市国府にある。国府は、地元の人でないと分からないのだが、「こう」と発音する。
f:id:bitterharvest:20170924135104p:plain

名鉄線の国府で降り、徒歩で20分程度のところに三河国分寺・尼寺がある。この日は幸い秋晴れで、散策しながら、訪れるのに最適であった。

名鉄豊川線豊川稲荷駅から国府駅行の2両電車で向かった。
f:id:bitterharvest:20170924135645j:plain

最初に訪れたのは三河国分寺である。ここは、野草が生い茂る広場だ。お彼岸のこの日、曼殊沙華がきれいに咲いていた。
f:id:bitterharvest:20170924135842j:plain

このお寺には、銅鐘以外に残されているものはない。
f:id:bitterharvest:20170924140317j:plain

しばらく、国分寺跡の周りを散策した後で、国分尼寺へ向かう。ここは、南大門が復元されている。
f:id:bitterharvest:20170924140827j:plain
f:id:bitterharvest:20170924140857j:plain

金堂や講堂の跡も復元されている。
f:id:bitterharvest:20170924141211j:plain
f:id:bitterharvest:20170924141251j:plain

国分尼寺を発掘する前には、これらの遺跡の上にお寺が立っていたそうだ。このお寺を敷地の横に立て直して、発掘作業をし、復元したと自称ボランティアのおじさんが説明してくれた。下の写真は立て直しされたお寺である。しかし、現在では、ご住職もなくなり、その奥さんもなくなってしまって、お坊さんのいないお寺になっているとのことであった。
f:id:bitterharvest:20170924141844j:plain

近くに資料館があったが、ボランティアのおじさんが、「見てもつまんない」と言うので、国府の駅に向かい、豊川稲荷駅に戻った。

豊川稲荷駅前には、お稲荷さんの狐の像がある。
f:id:bitterharvest:20170924143629j:plain
参道に沿っては、狐さんの好物の稲荷寿司のお店が多い。
f:id:bitterharvest:20170924143844j:plain

豊川稲荷は、神社かと思っていた。しかし、境内に入るとお経が聞こえる。「あれ」と思って、調べると曹洞宗のお寺だ。
曹洞宗法王派の東海義易によって1441年に創建され、室町時代の末期、今川義元によって伽藍が整備されたそうだ。

お寺にもかかわらず鳥居がある。ただし、見慣れた朱色ではない。
f:id:bitterharvest:20170924144640j:plain

本殿は立派だ。
f:id:bitterharvest:20170924144742j:plain

奥の院に向かう参道にはのぼりが立ち並んでいる。
f:id:bitterharvest:20170924144852j:plain

奥の院の先には霊狐塚がある。狐さんが沢山いる。
f:id:bitterharvest:20170924145012j:plain
f:id:bitterharvest:20170924145035j:plain

本殿の近くには、小さな庭園もあった。
f:id:bitterharvest:20170924145226j:plain

産まれたばかりの赤ちゃんの健康をお祈りして、病院へと向かった。

縄文時代後期、最も多数の人骨が発見された吉胡貝塚を訪ねる

先週、息子のところに孫が生まれたので、赤ちゃんを見がてら愛知県の三河地方を訪れた。以前から訪問したいと思っていた吉胡(よしご)貝塚には、9月22日に足を運んだ。

この貝塚は、大正11,12年に京都大学の清野謙次教授により、300体を超える人骨が発見されたところとして知られている(その後の発見も加えると340体)。
f:id:bitterharvest:20170924095136j:plain

日本で最も多くの人骨が発見された遺跡だ。吉胡貝塚渥美半島の中ほどにある。
f:id:bitterharvest:20170924093446p:plain

豊橋からは、豊橋鉄道渥美線を利用する。出発駅は新豊橋だ、電車は2両連結。かつて、東急電鉄で使われた懐かしい電車だ。それぞれの電車には、花の名前がつけられていて、我々の乗った電車は「椿」であった。
f:id:bitterharvest:20170924094639j:plain

終点の三河田原駅で降りる。運が悪いことに、この駅に近付いたころから小雨になった。
f:id:bitterharvest:20170924095044j:plain

街の景色を楽しみながら、田原城にも寄って、目的地に向かおうと思っていたが、雨に濡れるのが嫌なので、タクシーで直接、吉胡貝塚に向かった。タクシーが止まった先には、こじんまりとしているが、立派な資料館がある。
f:id:bitterharvest:20170924095951j:plain

資料館の中には、貝塚を発掘している様子も示されている。吉胡貝塚は、渥美半島の内部にあるように見えるが、近くまで田原湾が入り込んでおり、そばにはこの湾に流れこむ汐川がある。この貝塚縄文時代後期から晩期にかけての遺跡だ。この時期は暖かかった中期が終わり、寒冷期となる。多くの地域で人口が激減したが、この地域は住みやすかったのだろう。近くには、191体の人骨が発見された伊川津(いかわづ)貝塚や90体の稲荷山(いなにやま)貝塚がある。これらの貝塚も縄文後期から晩期の遺跡である。
f:id:bitterharvest:20170924100213j:plain

貝塚から採取された貝なども展示されていた。
f:id:bitterharvest:20170924104817j:plain

この時代には犬も飼われていたようで、人間の人骨と一緒に犬の骨も見つかっている。
f:id:bitterharvest:20170924105032j:plain

この地域の貝塚の特徴は、歯を抜いたり(抜歯)、削ったり(叉状研歯)する習慣があったことだ。ふじのくに地球環境史ミュージアムの日下宗一郎さんが、論文「縄文時代人の食性と集団間移動−安定同位体分析による試論」で、食性によって抜歯の仕方が異なっていたのではないかと報告している。また、刺青をする習慣もあった。
f:id:bitterharvest:20170924110932j:plain

このころは装飾も進み、貝殻を利用した指輪も発見されている。
f:id:bitterharvest:20170924112654j:plain

資料館の外は史跡公園になっている。
f:id:bitterharvest:20170924112834j:plain

昭和26年に発掘調査した現場も復元されている。5体の縄文人と2対の縄文犬だ(もちろん、骨は模型)。
f:id:bitterharvest:20170924113021j:plain

また、貝塚の平面もある。
f:id:bitterharvest:20170924113225j:plain

昭和26年の発掘で、右腕に4個、左腕に7個の指輪をした50歳前後の女性が発掘された。その場所も記されていた。
f:id:bitterharvest:20170924113454j:plain
なお、資料館にはこの女性の骨の模型がある。
f:id:bitterharvest:20170924120325j:plain
骨は入りいろな姿で埋葬されたようで、資料館には、盤上集骨墓も展示されていた。
f:id:bitterharvest:20170924121242j:plain

公園の風景をいくつか載せよう。
f:id:bitterharvest:20170924113559j:plain
f:id:bitterharvest:20170924113632j:plain
f:id:bitterharvest:20170924113735j:plain

公園の見学を終えたころ、雨が強くなってきたので、タクシーを呼んで、帰ることとした。

雨のため、予定より短い旅になってしまったが、渥美半島の訪問は初めてであったし、また、息子が勤務する会社の近くまで行け、それなりに楽しい旅行であった。

モノイドーHaskellでの表現

13 モノイド

モノイド圏は、集合の要素を射に、二項演算子を射の合成に、そして、単位元を恒等射にし、結合律、単位律が成り立っているものをいう。

例えば、整数の乗算の場合には、
1) 対象:シングルトン(通常星印で表される)
2) 射:整数
3) ドメイン、コドメイン:シングルトン
4) 恒等射:1
5) 合成: \(\times\)
① 結合律:満足
② 単位律:満足

しかし、上記の例でみたように射は集合の要素となるので、圏としては、余りにも細かいものが見えて気持ちよくない。そこで、構成要素を隠すように抽象化したい。しかし、いきなり、抽象化の議論を進めるのは気が引けるので、モノイドとは何かをHaskellを利用して慣れておこう。

13.1 Haskellでのモノイド

HaskellではモノイドはData.Monoidでクラスとして次のように定義されている。

class Monoid m where
  mempty :: m
  mappend :: m -> m -> m
  mconcat :: [m] -> m
  -- defining mconcat is optional, since it has the following default:
  mconcat = foldr mappend mempty

これに従えば、モノイドを使うときは\(mempty\)と\(mappend\)を定義する必要がある。なお、\(mappend\)は、中置関数\(<>\)でしばしば置き換えられる。

x <> y = mappend x y

また、結合律と単位律を満たさなけらばならないので、次の式を満たさなければならない。

-- Identity laws
x <> mempty = x
mempty <> x = x
-- Associativity
(x <> y) <> z = x <> (y <> z)

それでは、文字列の結合をモノイドとして定義しよう。次のようになる。

instance Monoid [a] where
  mempty = []
  mappend x y = x ++ y
  mconcat = concat

積は次のようになる。積は数(\(Num\))に対する二項演算子であるが、モノイドとして定義するときは、\(Product\)と呼ばれる新しいデータ型(コンテナ)を用意する。

newtype Product n = Product n
 
instance Num n => Monoid (Product n) where
  mempty = Product 1
  mappend (Product x) (Product y) = Product (x * y)

和(余積)は次のようになる。ここでも、\(Sum\)と呼ばれる新しいデータ型(コンテナ)を用意する。

newtype Sum n = Sum n
 
instance Num n => Monoid (Sum n) where
  mempty = Sum 0
  mappend (Sum x) (Sum y) = Sum (x + y)

それでは利用してみよう。なお、Data.Monoidでは\(getProduct,getSum\)により、演算結果を数(\(Num\))のデータ型で表す関数を用意している。

Prelude> import Data.Monoid
Prelude Data.Monoid> [1,2,3] <> [5,6]
[1,2,3,5,6]
Prelude Data.Monoid> [1,2,3] <> []
[1,2,3]
Prelude Data.Monoid> [] <> [2,3,4]
[2,3,4]
Prelude Data.Monoid> Product 3 <> Product 5
Product {getProduct = 15}
Prelude Data.Monoid> Product 3 <> Product 1
Product {getProduct = 3}
Prelude Data.Monoid> Product 1 <> Product 2.34
Product {getProduct = 2.34}
Prelude Data.Monoid> getProduct $ Product 32.5 <> Product 2.34
76.05
Prelude Data.Monoid> getProduct $ Product (3 / 4) <> Product ( 6 / 7)
0.6428571428571428
Prelude Data.Monoid> getSum $ Sum 4 <> Sum 5
9
Prelude Data.Monoid> getSum $ Sum 4 <> Sum 0
4
Prelude Data.Monoid> getSum $ Sum 0 <> Sum 5.5
5.5
Prelude Data.Monoid> getSum $ Sum ( 2.3 * 5) <> Sum (5.5 + 3.2)
20.2

横浜:吉田新田を訪ねる

今回(19日)は、江戸時代の初めに開発された吉田新田の跡を訪ねた。この場所は、明治になると伊勢佐木町を中心に日本でも有数の商業地として栄えることとなる。しかし、戦国時代が終わり、江戸幕府が開かれたころは、大きな入り海だった。

横浜には、関内と関外と呼ばれる二つの地域がある。吉田新田は関外の大部分を占める。現在のこの地域は、かつてほどの勢いはないが、前述したように、伊勢佐木町よこはまばしなどの商店街を有する繁華街だ。また、関内は山下公園や横浜球場や中華街を有するデート・スポットだ。地図で示そう。
f:id:bitterharvest:20170920102134p:plain
地図で、左下隅から上中央に向かって水色の線がある。これは大岡川である。また、同じ左下隅から右中央に向かって薄茶色の線がある。これは高速道路だ。高速道路と大岡川に挟まれた区域が関内と関外である。高速道路は途中の石川町で分かれて大岡川の方に向かう線がある。これに沿って、関内と関外とに分けられ、右上の海側の方が関内で、左下の陸側の方が関外である。高速道路の下は中村川が流れている。この川はまっすぐに海に流れるわけではなく、高速道路が別れるところで直角に折れて、大岡川の方に向かって流れる。この部分は派大岡川と呼ばれ、関内と関外を分ける。

江戸時代初めの関外は海よりの半分程度のところまでしか小舟が入れないような浅瀬の入り海だった。また、関内は、高速道路の側から砂州が伸びていて、入り海を閉じ込める役割をしていた。この砂州とその近隣は、浜が横に伸びている形をしていたのでそのように呼ばれたものと思うが、室町時代から横浜村と呼ばれるようになった。江戸時代の終わりごろまでは現在とは全く異なり寒村であった。

関内・関外と呼ばれるようになったのは、江戸時代の終わりである。黒船が来航し、その結果、1859年に日米通商条約によって横浜は開港され、山下公園や中華街を含む地域が外国人居留地となる。このとき、横浜村の砂州の根元に堀が構築され(高速道路の分岐点から海に向かっている箇所)、堀川と呼ばれた。外国人居留地大岡川、派大岡川、堀川で囲まれ、出入りするためには関所(吉田橋)を通らなければならなかった。そのため、この三つの川で囲まれた地域を「関の内」ということで、関内と呼ばれるようになる。なお、関内は、海に向かって右半分が外国人が住む町で、左半分が日本人の居住地となった。地図を見ると、道と道の間隔が異なるので、その差は歴然としている。

関外は関内と対比的に「関の外」ということで関外と呼ばれるようになった。吉田新田は大阪生まれの江戸の材木商である吉田勘兵衛(1611-1686)により、1656年に開発が始められる。翌年、大岡川の決壊で堤防が壊れ、1659年に改めて工事をやり直し、1667年に完成する。最初の工事を始めてから11年の歳月を要した。開発前の入り海とその時の計画図と思われるもの示したのが下図である。
f:id:bitterharvest:20170920111151p:plain

上の図は、最初に示した横浜市の図を右回りにおよそ90度以上回転してある。入り海は釣鐘のような形をしている。また、開港前の関内・関外は次のようになっていた。
f:id:bitterharvest:20170920112448p:plain

吉田新田の大きさは115万平米、丁度、東京ディズニーランドとディズニーシーを合わせたぐらいの大きさだ。石高は1000石だ。1石はおよそ150Kgなので(1石は大人が1年間に食べる量)、約150トンとれたことになる。20トン積の大型トラックで7~8台となる。

鬼頭宏さんによれば、江戸時代が始まった1600年の日本の人口は1227万人、1700年には2829万人、1721年には3128万人、1873年(明治6年)には3330万人である。江戸幕府が開かれて最初の120年間で人口は2.5倍に膨れ上がり、その後の150年は横ばいとなる。このため、江戸時代前半には、人口急増に対応するため、各地で新田開発が行われた。吉田新田もその一例である。

吉田新田の開発は、堤を築いて川を両脇に押しのけ、内部を干上がらせるという工法で行われた。内部の水を抜くために、潮の干満に応じて堰の上げ下げもなされた。また、付近の山からの土砂で埋めることも同時に行われた。この当時の土木技術は戦国時代に築城が盛んにおこなわれたこともあり、高度に発達していたようだ。

前置きが長くなったが、吉田新田巡りは桜木町の駅から大岡川を分離する地点まで、地下鉄5駅(桜木町、関内、伊勢佐木長者町、坂東橋、吉野町)の周囲をあちこち巡りながら3時間歩いた。最初にめざした地点は、野毛山公園である。一段と高くなっているところで、展望台からは、吉田新田の跡を一望に見渡すことができる。

公園までの道のりにいくつかの名所があるので、それも併せて見学した。最初の訪問場所は、一昨年改修工事が完了し、新装なった成田山横浜別院である。ここは、易断で知られる高島嘉右衛門の協力により明治3年(1870)に建立された。
f:id:bitterharvest:20170920200401j:plain

さらに、伊勢山皇大神宮を訪れる。この神宮も明治3年に創建された。
f:id:bitterharvest:20170920200451j:plain

野毛山公園に入ると、横浜市の水道事業に貢献した英国人技師ヘンリー・スペンサー・パーマーの胸像がある。彼は、明治18年(1985)に相模川の支流の道志川を水源にして、野毛山配水池までの48Kmにわたる水道建設を開始し、同20年に日本で最初となる近代水道を完成させた。
f:id:bitterharvest:20170920201811j:plain

昭和38年(1963)には東京オリンピックが開催されたが、蹴球・バレーボール・バスケットボールの予選が横浜で行われた。それを記念する碑が公園にある。
f:id:bitterharvest:20170920202821j:plain

公園にある展望台に上って、吉田新田のあった地域を一望する。ビルが隙間なく立ち並んでいる。
f:id:bitterharvest:20170920202916j:plain
f:id:bitterharvest:20170920202938j:plain

公園を離れて、大岡川にかかる長者橋へと歩みを進める。この辺りは、吉田勘兵衛の屋敷跡である。今でも、子孫が経営する吉田興産や吉田パーキングの建物がある。
f:id:bitterharvest:20170920203539j:plain
f:id:bitterharvest:20170920203559j:plain

また、吉田家の庭先にあった大井戸を見る。この井戸は、200年にわたって付近の住民の飲料水になったとのことである。
f:id:bitterharvest:20170920204005j:plain

吉田新田は今は商業地となっているが、その中でも一番賑やかである伊勢佐木町を歩く。ここには、明治時代に創業したお店が何軒かある。
f:id:bitterharvest:20170920204209j:plain
f:id:bitterharvest:20170920204238j:plain
f:id:bitterharvest:20170920204321j:plain
f:id:bitterharvest:20170920204358j:plain

また、青江三奈伊勢佐木町ブルースの碑もある。
f:id:bitterharvest:20170920204513j:plain

吉田新田の真ん中あたりには、よこはまばし商店街がある(写真は大通公園から商店街を撮った)。
f:id:bitterharvest:20170920204645j:plain

よこはまばし商店街は平日にも関わらす、多くの人でにぎわっていた。
f:id:bitterharvest:20170920205320j:plain

黄金町まで進むと珍しい旗屋さんがある。
f:id:bitterharvest:20170920205615j:plain

吉田新田開発後に最初にかけられた橋が道慶橋だ。写真は道慶橋の上から大岡川を撮ったものだ。
f:id:bitterharvest:20170920211016j:plain

大岡川の分離地点の近くには、吉田新田の守護神として1673年に創建された「お三の宮日枝神社」がある。数日前にお三の宮の祭が行われたため、まだ、飾りが残っていた。
f:id:bitterharvest:20170920211336j:plain
f:id:bitterharvest:20170920211401j:plain

また、境内には創建当時の手水鉢がある。
f:id:bitterharvest:20170920211605j:plain

さらに、近くには用水堰の守り神社としての堰神社がある。
f:id:bitterharvest:20170921083454j:plain

さて、最後の目的地、大岡川中村川大岡川に分かれる地点に到着した。真ん中の奥が分流前の大岡川。左側が中村川、右側が分流後の大岡川である。

台風一過が続く暑い中、3時間余りの行程は無事終了した。
f:id:bitterharvest:20170920211801j:plain

キャスト・アウェイ (Cast Away) - 4年間の無人島での漂流生活の後に彼を待ち受けていた不条理な運命!

今回紹介する映画は2000年制作のトム・ハンクス演ずるキャスト・アウェイだ。

俳優の役作りはすごいなあと感じさせられる時がある。この映画もその一つだ。映画が始まるとすぐに、宅急便のトラックからトム・ハンクス扮するチャックが下りてくるが、こんなに大きな図体だったかと疑問を抱かせ、思わず尋ねたくなる場面がある(写真は少し後の場面でロシアで仕事に従事しているときのもの)。
f:id:bitterharvest:20170915055441j:plain
後で調べると、中年太りの男性を演ずるために50ポンド(23Kg)も体重を増やしたそうだ。

そして、ビジネスマン役での場面どりを終了した後、1年間ほど撮影を中断し、その間に髪や髭をぼうぼうにはやし、無人島での役に備えたとのことだ。
f:id:bitterharvest:20170915055501j:plain

タイトルのキャスト・アウェイは、「世の中から見捨てられた人」、あるいは、「船が難破したため漂流させられた人」を意味する。飛行機が海上に不時着し、救命ボートで無人島に到着し、そこで、4年間の孤独な生活を送ることになるので、「漂流者」と訳すのがよいと思う。しかし、無人島での生活の後に待ち受けているのは不条理な現実だ。観客が最も心を打たれる場面はここなので、この部分を強調すると、「見捨てられた人」と言いたかったのではと感じさせられる。

映画のあらすじは次のようになっている。

クリスマス・パーティーをしている最中に、緊急の仕事が入ったチャックは、恋人のケリー(ヘレン・ハントが演ずる)に大みそかには帰ってくると言って、貨物便に乗り込む。その時、ケリーの祖父からの形見である懐中時計がクリスマス・プレゼントとして渡される。そして、時計のふたの裏側にはケリーの写真が貼ってある。

運の悪いことに、飛行機は太平洋上で墜落し、チャックは救命ボートで無人島に漂着する。待っていたのは、生きていくための過酷な戦いと、襲ってくる孤独との葛藤である。バレーボールについた手形の血判に目や口を描き、ウィルソンと名付けて仲間として擬人化し、ケリーの写真に望みを託して、4年間生き抜く。
f:id:bitterharvest:20170915091554j:plain

映画のほとんどは4年間の無人生活が占めており、トム・ハンクスが素晴らしい演技をする。

やっとの思いで故国に戻るが、4年間という歳月は残酷である。思い焦がれていたケリーは、すでに結婚していて子供もいる。

帰国した後、チャックは思い切ってケリーに会いに行く。彼女の自宅での会話がこの映画の中で最も感動する場面だが、ここは読者に残しておこう。ただし、最後の場面は次のようになっている。彼が去ろうとして車を走らせ始めたとき、激しい雨の中をケリーが走って追ってくる。二人は抱き合い、そして、ケリーは車に乗り込む。この時のケリーの演技が圧巻で、激しい心臓の鼓動に伴って、上半身が強く脈動する。ケリーのきっぱりとした決断が伝わってくる。
f:id:bitterharvest:20170915112358j:plain
二人はこのまま走り出すのかと思った瞬間、チャックが言う。

"You have to go home."
「家に戻りなさい。」

場面は変わって、チャックが友人にこの時の決着について説明する。

"We both had done the math, and Kelly added it all up."
この文章は意訳しないと分かりにくい。直訳すると、「二人とも計算したんだ。そして、ケリーは合計を得るまであれもこれも加えたのだ。」となるのだが、分かったようで、分からない気分にさせられる。使われている単語は易しいのだが、すんなりと頭に入ってくる表現ではない。

このため、頭をひねって考える必要がある(この「ひねる」という日本語の表現は同じように外国人には通じにくい)。計算するということは、頭脳をフル回転させるという意味に転じる。合計を得るまであれもこれも加えたということは、様々なことを考慮に入れて納得のいく結論を出すという意味に転じる。従って、
「二人とも熱心に考えたのだ。そしてケリーはケリーなりに納得のいく結論を導き出した。」

"She knew she had to let me go."
「ケリーは俺を去らさせなければいけないと分かっていた。」
"I added it up, knew that I'd lost her."
「俺も納得のいく答えを出した。ケリーを失うということを分かっていた。」
"'Cause I was never gonna get off that island."
「だって、島からは決して脱出できないのだから。」

そして、チャックは無人島で考えていたことを話し始める。
"I was gonna die there, totally alone."
「たった一人で、島で死んでいくだろう。」
"I mean, I was gonna get sick or I was gonna get injured or something."
「そう、病気になるか、怪我をするか、あるいは、他のことで。」
"The only choice I had, the only thing I could control was when and how and where that was gonna happen."
「俺が持っている唯一の選択は、俺が意図的にできる唯一のことは、いつ、いかにして、どこで、それを行うかということだけだ。」
"So I made a rope."
「そこで、ロープを作った。」
"And I went up to the summit to hang myself."
「そして、首つり自殺するすためにいただきに行った。」
"But I had to test it, you know ?"
「でも、まず試してみる必要があった。」
"Of course."
「もちろん。」
"You know me."
「俺の性分を知ってるだろう。」
"And the weight of the log snapped the limb of the tree."
「(身代わりに使った)丸太の重さが木の枝を折ってしまった。」
"So I couldn't even kill myself the way I wanted to."
「なので、俺が望んだ方法で自殺することさえできなかった。」
"I had power over nothing."
この言い回しも、やさしい単語の組合せなのだが、意味が分かりにくい。"over nothing"は「つまらないことに」を表す。例えば、次のような使い方をする。
"She got exited over nothing."
「彼女はつまらないことに興奮した。」
従って、ここでの"I had power over nothing."の意味は、
「何もできなかった。」
独白はまだ続くが、このあとは映画を観て欲しい。

最後の場面では、中年太りの男性ではなく、見慣れたトム・ハンクスに戻っている。
f:id:bitterharvest:20170915112321j:plain

今回の会話の部分には、なじみの少ない慣用句が二つあった。Googleで検索すると、英語を母国語としている人も質問しているので、知らない人も多いようだ。このような慣用句に出くわすと、何を言っているのかなあと考えだすために、次の言葉が入ってこなくなる。ついていけない状態に陥るのだが、聞き流して、前後関係から理解することが肝要だ。

モナドと自然変換

12 圏論でのモナド

Haskellでのモナドについて論じてきたが、モナド圏論の中の一つの圏でもある。そこで、ここではモナドが圏となるための条件を求めて見よう。

圏論においては自然変換は重要な役割をなすが、モナドは自然変換が主要な枠組みとなっている数学概念でもある。

まず、Haskellではモナドは\(join\)と\(return\)を用いて次のように定義した。

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

また、Haskellでの記述を圏論での記述に直すことを考えよう。モナドは自身の圏に写像する関手である。そこで、モナドの圏\(\mathcal {C}\)は、対象が関手で、射が関手間の写像(自然変換)として構成することにしよう。そして、Haskellモナド\(m\)は圏論での関手\(T\)として、\(join,return\)は圏論での自然変換\(\mu\), \eta として表すことにしよう。

下図の互換図に示すように、\(return\)では、\(a\)は恒等関手\(Id\)となり、\(m \ a \)は関手\(T\)となる。
f:id:bitterharvest:20170905115110p:plain
これより、\eta :: Id \rightarrow Tとなる。 \etaは、\(\mathcal {C}\)の内部構造を維持しての関手から関手への写像となっているので、自然変換である。

また、\(T \circ T\)を\(T^2\)と書くと、下図の互換図より\(\mu:: T^2 \rightarrow T \)を得る。これも内部構造を保っての関手から関手への写像なので、自然変換である。
f:id:bitterharvest:20170905115136p:plain
ところで、モナドが圏となるためには、\(T\)に対して結合則が成り立つ必要がある。即ち、\(T \circ T^2= T^2 \circ T\)でなければならない。それでは、左式を\(\mu\)を用いて表してみよう。

\(T \circ T^2\)を互換図で表すと次のようになる。なお、下図で\(T\)から\(T\)への恒等な自然変換を\(I_T\)で表した。
f:id:bitterharvest:20170905115156p:plain
この互換図より、\(\mu \circ I_T=\mu \)を得る。

\(T^2 \circ T\)を互換図で表すと次のようになる。
f:id:bitterharvest:20170905115213p:plain

この互換図より、\( I_T \circ \mu =\mu \)を得る。
これより\(\mu \circ I_T=\mu = I_T \circ \mu \)が満たさなければならないことが分かる。

これを互換図で表現すると次のようになる。
f:id:bitterharvest:20170905115233p:plain

モナドが圏であるためには、関手\(T\)は単位律をも満たす必要がある。単位律は\(T \circ Id = T = Id \circ T\)である。

\(T \circ Id \)より下図の互換図を得る。
f:id:bitterharvest:20170905115248p:plain
これより I_T \circ \eta = \etaを得る。

\(Id \circ T \)より下図の互換図を得る。
f:id:bitterharvest:20170905115312p:plain
これより \eta \circ I_T = \etaを得る。

従って、 I_T \circ \eta =\eta = \eta \circ I_T が満たさなければならないことが分かる。

これを互換図で表現すると次のようになる。
f:id:bitterharvest:20170905115331p:plain

\(I_T\)は\(T\)と見なしてもよいので、Mac LaneやAwodeyの教科書には次の互換図が掲載されている。
f:id:bitterharvest:20170905115350p:plain


最後に圏論としてのモナドをまとめよう。

モナドの圏としての構成:
1) 対象:関手\(T\)、恒等関手\(Id\)
2)射:\(\mu\), \eta
3)ソース、ターゲット:\(\mu: T^2 \rightarrow T\), \eta:: Id \rightarrow T
4)結合:\(\mu\)と \etaを結合したものは、下図に示すように、組合せによらず\(\mu\)となる。
f:id:bitterharvest:20170905115410p:plain
5)恒等射:\(I_T: T \rightarrow T\)

さらに、次の二つを満たす必要がある。
\( \mu \circ T = T \circ \mu \)
 T \circ \eta =\eta = \eta \circ T

以上で、圏論におけるモナドの構造を示すことができた。

横浜関内の歴史的建築を訪ねる

9月1日は東京や横浜に住んでいる人々にとっては忘れてはならない日だ。94年前(大正12年)のこの日、関東大震災が発生した。地震の規模はマグニチュード7.9、東京・横浜の震度は6だ。発生した時間は11時58分、丁度お昼時だ。

震源地は相模湾で、震源に近かった横浜は東京よりも大きな被害を被った。当時の横浜市(市域は中心部のみ)の人口は約42万人で、全壊した住家は16,000棟、死者は23,000人であった。地震によって倒壊した家が多く、また、その後に生じた火災により死亡した人も多い。

防災の日に、神奈川県立歴史博物館が「横浜正金銀行と横浜関内の関東大震災復興建設をめぐる」という見学会を開催したので、それに参加した。現在の神奈川県立歴史博物館から山下公園までの震災後の復興に関連する建物を見学した。

神奈川県立歴史博物館となっている建物は、大震災の頃は横浜正金銀行本店の本館であった。
f:id:bitterharvest:20170902090249j:plain

この本館は、関東大地震では倒壊しなかった。付近では火災が発生し、それを避けるために、正門から100人、通用門から100人が逃げ込んだそうだ。13時には延焼から逃れるために鉄の頑丈な扉は閉じられる。建物の中には、行員140人と逃げ込んできた200人、合わせて340人が避難していた。

火勢は強く、本館の上階に火が入り、内部が燃え始めた。避難している人たちは上の階から下の階へ、そして、地下室へと逃げ込んだ。火事の熱で地下室はとても熱く、炊事場の桶に貯めてあった水を唇に浸すなどして耐えたそうだ。15時半ごろには1階まで火は広がったが、幸いに、地下室には延焼せずに16時半ごろには内部の火災は治まった。周辺も鎮火しており、外へ出たとのことだ。また、門扉が閉まった後に、本館に駆けつけてきた人々もたくさんいたようで、本館の周りにはいくつもの死体があったそうだ。

横浜正金銀行の本館は1904年(明治37年)の竣工で、設計者は妻木頼黄(つまき よりなか)である。彼は明治時代の3巨頭の一人と言われる建築家で、大蔵省をはじめとする数多くの官庁建築を手がけた。赤レンガ倉庫も彼の設計である。国会議事堂を設計することを望んでいたが、その夢は果たされなかった。

本館は古典主義様式である(古典主義は、ルネサンス建築、バロック建築新古典主義建築などの総称で、古代ローマ時代に建てられたオリジナルの建築の知識を発展、再構成した建築を言う)。コリント式の柱を巡らせ、大きなドームを持つネオ・バロック式の建築である。建物の高さは17mであるのに対し、ドームは19mもある。

隣にあるのが、旧川崎銀行横浜支店の建物だ。
f:id:bitterharvest:20170902102105j:plain
この建物は1922年(大正11年)に竣工している。関東大震災では被害を受けなかった。妻木の弟子である矢部又吉が設計した。ファサード2面を残して建て替えられている。元の建物は古典主義様式である。

次に見学したのは旧横浜銀行集会所である。現在は横浜銀行協会が利用している。
f:id:bitterharvest:20170902105305j:plain
妻木の弟子で国会議事堂を設計した大熊義邦と彼の娘婿の林豪蔵が設計し、1936年(昭和11年)に竣工している。銀行の建物は古典主義様式であった時代に、幾何学模様をモチーフにしたアール・デコ調のデザインを取り入れている。

横浜郵船ビルも1936年の竣工である。この界隈では、最後の古典主義様式の建物である。f:id:bitterharvest:20170902110901j:plain
コリント式のがっしりとした柱が特徴である。

さらに進んで、神奈川県庁に行く。
f:id:bitterharvest:20170902111346j:plain
何度か立て直しされているが、これは4代目の県庁である。1928年(昭和3年)の竣工で、コンペを行い一等当選案に基づいて設計された。古典主義を用いているが、細部では、スクラッチタイル張りの外壁や屋根の銅板による幾何学的な様式などアール・デコを取り入れ、新しい表現法に挑んでいることが感じられる。また、上部には和式の塔を戴いているのも特徴である。

県庁の内部にも面白い装飾がある。正面の階段の両翼に装飾灯がある。
f:id:bitterharvest:20170902112540j:plain
柱にも和風の装飾がある。
f:id:bitterharvest:20170902112705j:plain
4階の階段を上がりきったところにも装飾がある。
f:id:bitterharvest:20170902112758j:plain
戦前は、正庁だったそうだ。この装飾の反対側には天皇の御影が掲げられていたそうだ。

屋上に上ると、港や付近の建物を展望できる。
横浜三塔を見学する。キングの塔は、神奈川県庁の上にある塔である。
f:id:bitterharvest:20170902113817j:plain
クイーンの塔は横浜税関本関庁舎の塔である。建物はアール・デコである。
f:id:bitterharvest:20170902114035j:plain
ジャックの塔は、開港記念横浜会館の塔である。
f:id:bitterharvest:20170902114211j:plain
塔の下の建物は辰野式と呼ばれるフリークラシック・スタイルである。
f:id:bitterharvest:20170902114324j:plain

県庁の屋上からは象の鼻も見ることができる。
f:id:bitterharvest:20170902121250j:plain
幕末末期の開港に伴って、イギリス波止場が作られたが、その形が象の鼻に似ていたことから名づけられた。改造されてこのような形でない時もあったが、2009年に復元された。

最後の見学場所はニューグランドホテルである。
f:id:bitterharvest:20170902121906j:plain

震災の後に、外国人求まれるようなホテルということで、地元の有志が資金を出し合って開設したとのことであった。古典主義様式をとりながらも、アール・デコ調でまとめられたホテルである。丸みを帯びたコーナーには竣工年を刻んだ浮彫がある。

若いころからよく訪れたルートであったが、建築物にこれほど注目して見学したことはない。この時代の主要な建築様式を学ぶことができ、有意義な一日であった。

ビフォア・サンセット(Before Sunset)-9年後の再開

ビフォア・サンセットは、恋人までの距離(Before Sunrise)の続編で、9年後に作られた。
f:id:bitterharvest:20170828103807j:plain

前作で、イーサン・ホークが演ずるアメリカ人青年ジェシーと、ジュリー・デルビーが演ずるフランス人女性セリーヌは、ブタペストからウィーンに向かう国際列車の中で出会う。ジェシーは次の日にウィーンからアメリカに帰国し、セリーヌはこの列車でそのままパリに帰ろうとしている。ウィーン中央駅に着いたときに、この街を一緒に散策しないかとジェシーセリーヌを誘う。意気投合した二人は、ウィーンの街を歩きながらひたすら会話を続ける。夜が明けて、パリに向かうセリーヌジェシーはウィーン中央駅で見送る。その時、二人は6か月後に再びこの場所で会おうと約束する。ここまでが前作のあらすじだ。

ウィーンで別れてから9年の歳月がたち、ジェシーはウィーンでのエリーヌとのロマンスを題材にして小説を書く。この小説のプロモーションのために、ジェシーはパリの書店で開催されたワークショップで作者として講演する。
f:id:bitterharvest:20170828104751j:plain
講演が終わろうとする頃、思いがけずエリーヌが入ってくる。
f:id:bitterharvest:20170828104959j:plain
ジェシーはその日の便で帰国しなければならないため、あまり時間が残されていない。それでも、コーヒーでも飲もうということで二人はカフェに向かう。前回と同様に、会話がずっと続く。その中で、再開を約束した場所に来たのかが話題になる。セリーヌジェシーが来なかったものと思いこんでいる。いや、そうあってほしいと思っている。セリーヌはブタペストのおばあさんが亡くなり、再会を約束したその日に埋葬されたので、とても残念だったが行くことはできなかったと自身の理由を説明する。さらに、でもあなたも来なかったのよねと言い、それには特別の理由があったはずだわと言ったあと、彼の表情を読み取って続ける。

“Oh...no! No, you were there, weren't you?”
「え、うそ。来たのね。そうでしょ。」

セリーヌはさらに慌てて、
“Oh, no, that's terrible!”
「それは大変だわ。」

心の動揺を抑えきれずに、
“I know I'm laughing, but I don't mean it!”
「笑ってるけど、そのつもりではないの。」

さらに続けて、
“Did you hate me? You must have hated me. Have you been hating me all this time? You have!”
「私のこと嫌ったでしょう。嫌いだったに違いない。私のことずっと嫌いだったでしょう。間違いないでしょう。」

ジェシーは、少し楽しみながら、否定する。
“No, no...”
「そんなことはないよ。」

セリーヌは慌てている様子を隠さずに、
“Yes, you have! Oh, but you can't hate me now, right? I mean, my grandma...”
「嫌ってたわよ。でも、今は嫌えないわよ。だって、おばあさんが…。」

ジェシーは会話を楽しみながら、
“I don't hate you, alright? Come on, it's no big deal, alright? I flew all the way over there, you blew the thing off, and then my life has been a big nosedive since then, but I mean it's not a problem.”
「嫌ってないよ。だって、大したことではないもの。再開を約束した場所へはるばると飛んできたんだ。そして、あなたが全てをぶち壊してくれた。それ以来、私の人生は急降下しているよ。でも、問題という訳ではないよ。」

セリーヌが本当に悪いことをしたという気持ちになって、
“No, you can't say that!”
「そんなこと言わないで。」

さらに続けて、
“Oh, I can't believe it, you must have been so angry with me... I'm so sorry, I really wanted to be there, more than anything in the world! I swear...Honestly, I swear...I mean, you can't be angry...my grandmother...”
「信じられないわ。あなたはずっと私に対して怒っているでしょう。本当にごめんなさい。世界中のどのようなことよりも優先して、そこに行けたらと本当に思っていたの。誓ってよ。本当に、誓ってよ。あなたは怒ってはいけないわ。だって、おばあさんが..。」

ジェシーはちょっと行き過ぎたかなともって、
“No, I know, I know, I honestly thought that something like that might have happened. I was definitely bummed, but...Mostly, I was just mad we hadn't exchanged any phone numbers or any information.”
「分かっているよ。そのようなことが起こったのだろうと本当に思ったよ。確かに落ち込んだよ。なぜ、電話番号か連絡先を交換しなかったのだろうととても悔やんだよ。」

40秒という短い会話だが、再会を果たせなかったことをお互いにとても悔やんでいる様子がこの会話に凝縮されている。二人はこのあと、もし再会できていたら人生は変わっていただろうと思いながら会話を続けていく。

さて、ジェシーは何事もなく帰国できただろうか。

この映画、軽快に会話がずっと続く。でも、一語一語しっかり聞こうとすると、会話にスピードがあってちゃんと把握するのが大変だということに気がつく。会話のリズムが心地よい映画だと納得する。

また、会話の部分には、'hate'という単語を、過去形、現在完了形、現在形に使い分けて、言い訳をしている。弁解をするときはこのようにしたらよいのだなととても参考になる。

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

子供の頃に喉から手が出るほどに欲しかったものを多くの人は覚えているだろう。それも自分の小遣いでは到底手が届かないほど高値のものを切望したのではないだろうか。デパートと言われるものが全盛だった頃、おもちゃ売り場は何とも楽しい場所だった。その中でも、動く鉄道模型は人気の場所だった。ジオラマという言葉はまだ使われていなかったので、なんと呼ばれていたのかは今となっては分からないが、線路を切り替えて機関車を思い通りに走らせることができるレイアウトはいつも子供たちで一杯だった。

そんな子供の時の夢をかなえるために、2年がかりでジオラマを作成した。孫にも一部手伝わせて、この夏やっと完成した。とりあえず、動画を楽しんで頂こう。

路線は外側の新幹線と内側のローカル線だ。線路は9mmの幅しかないけれども、4両編成の新幹線が脱線することもなく走行してくれる。新幹線も線路も精巧にできているのだなあと感心する。ジオラマは右側が里山、中央が都会、左側が下町という設定だ。広い場所を必要とするので、夏休み中だけ家族に公開している。

3.5 銀行のATMを完成させる

それでは、利息の計算まで含めてプログラムを完成することにしよう。状態の方は、利率\(i\)と現在高\(c\)のタプル\((i,c)\)となる。また、入力は、預金高\(d\)と前回の預け入れからの期間\(p\)のタプル\((d,p)\)となる。銀行口座を開設する関数\(initdep\)は入力の部分を新しいタプルに変えるだけなので次のようになる。

initdep :: (Floating a, Floating b, Floating c, Floating d) => (c, d) -> State (c, d) (a, b)
initdep (i,c) =  put (i,c) >>= \_ -> return (0,0)

それでは預金の関数\(deposit\)を作成しよう。これも、入力と状態のところを新しいタプルに変えるだけなので、次のようになる。

deposit :: (Floating a, Floating b) => (b, b) -> State (b, b) (a,a)
deposit (d,p) =  get >>= \(i,s) -> put ((i,s + d + i ** p)) >>= \_ -> return (0,0)

利用してみよう。開設時の預金額を500とし、利率を1.001としよう。そして5期間経過した後に30預金したとしよう。これは、開設時の関数\(initdep\)と預金の関数\(deposit\)を結合すればよいので次のようになる。

f = (\a -> initdep a) >=> (\_ -> deposit (30,5))
*Main> runState (f (1.001, 500)) (0,0) 
((0.0,0.0),(1.001,531.005010010005))

うまく動いているようだ。さらに、4期間経過した後に20預金したとしよう。これはさらに預金の関数\(deposit\)を結合すればよいので次のようになる。

*Main> g = (\a -> initdep a) >=> (\_ -> deposit (30,5)) >=> (\_ -> deposit (20,4))
*Main> runState (g (1.001, 500)) (0,0) 
((0.0,0.0),(1.001,552.009016014006))

大丈夫のようだ。
それでは、引出の関数\(withdraw\)を作成しよう。これは預金が加算であったのに対し、減算となるので次のようになる。

withdraw :: (Floating a, Floating b) => (b, b) -> State (b, b) (a,a)
withdraw (d,p) =  get >>= \(i,s) -> put ((i,s - d + i ** p)) >>= \_ -> return (0,0)

それでは、さらに6期間経過した後で50引出したとしよう。同じように次のようになる。

*Main> h = (\a -> initdep a) >=> (\_ -> deposit (30,5)) >=> (\_ -> deposit (20,4)) >=> (\_ -> withdraw (50,6))
*Main> runState (h (1.001, 500)) (0,0) 
((0.0,0.0),(1.001,503.015031034021))

これで基本的な部分は終了である。

\(initdep,deposit,withdraw\)がコマンドのように利用されているのが分かることと思う。これを強調するには糖衣構文を作成して、利用するとよい。Haskellでは\(do\)という構文が用意されている。
また、上記のプログラムをより洗練するためには、金額は通常は正の整数なのでそのための処理も加える必要がある。これについては、読者の方で試みて欲しい。

プログラムの全体は次のようになっている。

(>=>) :: (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 :: (Floating a, Floating b) => (b, b) -> State (b, b) (a,a)
deposit (d,p) =  get >>= \(i,s) -> put ((i,s + d + i ** p)) >>= \_ -> return (0,0)

withdraw :: (Floating a, Floating b) => (b, b) -> State (b, b) (a,a)
withdraw (d,p) =  get >>= \(i,s) -> put ((i,s - d + i ** p)) >>= \_ -> return (0,0)

initdep :: (Floating a, Floating b, Floating c, Floating d) => (c, d) -> State (c, d) (a, b)
initdep (i,c) =  put (i,c) >>= \_ -> return (0,0)

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

お店でバターナッツかぼちゃを見つけた。昨年はハローウィンの頃だったので、今年は随分と店に出てくるのが早い。
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\)と呼ばれる二つの関数で実現される。