bitterharvest’s diary

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

キャスト・アウェイ (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)恒等射:\(I_T: T \rightarrow T\)
5)合成:\(\mu\)と \etaを合成したものは、下図に示すように、組合せによらず\(\mu\)となる。
f:id:bitterharvest:20170905115410p:plain

さらに、次の二つを満たす必要がある。
\( \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\)と呼ばれる二つの関数で実現される。

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

10.3 プログラムの合成

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

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

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

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

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

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

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

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

module Program ((Program..))) where

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

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

import Program as P

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

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

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

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

即ち、次のようにする。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

10.2 集合と冪集合

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

10.1 圏論とプログラム

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

return :: a -> m a 

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

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

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

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

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

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

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

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