プログラマは○学生 ***登場人物*** ほのか・・・魔法少女を夢見る○学3年生。 少し前までは平凡な○学生だった。 ひより・・・謎多き○学3年生。激しい関数型言語支持者(ファンクショノイド)である。 λさま・・・ラムダ計算の神様。 京都の東山に住んでいる。 ***あらすじ*** 今や時代はIT社会。魔法もプログラムで制御する時代です。 この物語は二人の○学生がプログラマを目指すお話です。 ちなみに、登場する女の子は全員18歳以上だよ。 ***第1話 ラムダ計算は突然なの*** [ほのか]大変! 大変! 大変だよ! ひよりちゃん! [ひより]どうしたの? 落ち着いて、ほのかちゃん。 [ほのか]あのね、なんか魔法少女になるにもプログラム学ぶ必要があるらしいの。 [ひより](人工知能搭載のデバイスとか闇の書とかが出てくるやつの話かな?) [ほのか]でね、プログラミングを勉強しようと思ったんだけど…… [ひより]どうしたの? ベルカ式プログラミングの勉強がしたいの? [ほのか]違うの! 覚えることが多すぎるの! ひよりちゃん、なんとかしてよ! [ひより]えーと、ぐーぐる先生に聞いたらJavaScriptが簡単って書いてるよ。 [ほのか]それでも覚えることが多すぎるよ〜。     コンピュータって計算機でしょ? 計算なんて指を使うだけで十分なんじゃないの? [ひより]つまり、指使って計算するようなことを計算機の上でできたらいいのかな? [ほのか]そう!多分それがしたいの。 よく分からないけど。 [ひより]えーとね、JavaScriptは関数ってやつだけ覚えればそれができるんだよ。 [ほのか]そうなの!? じゃあ、それだけ覚えるね♪ [ひより]とりあえず、下の三つは覚えてね。 zero = function(g) { return function(u) { return u; }; }; suc = function(x) { return function(g) { return function(u) { return g((x(g))(u)); }; }; }; pred = function(x) { return function(g) { return function(u) { return ((x(function(a) { return function(b) { return b(a(g)); }; }))(function(w) {return u;}))(function(c) {return c;}); }; }; }; [ほのか]……私、騙されてない? これって簡単なの? [ひより]気のせいだよ。少し読みにくいのはカリー様が見てるからしかたないんだよ。 [ほのか]それなら仕方ないね。 (カリー様ってカレーの神様かな?) [ひより]まず最初に、"zero"っていうのが指を一本も立てないグーの形を表すことにしてるんだよ。 [ほのか]要するに0だね。 そう言われるとだんだんグーに見えてきたよ。 [ひより]"suc(zero)"って書くと、そこから指を一本立てた形、つまり1になるんだよ。 [ほのか]うわー、凄く簡単だね♪ [ひより]"suc(suc(zero))"って書くとそこからさらに指を一本立てて2になるんだよ。 [ほのか]つまり"suc(手の形)"ってかくとそこから指を一本立てるわけだね。 (10を超えたらどうしよう…) [ひより]pred(手の形)って書くと、その逆に指を一本下ろすの。     だから"pred(suc(suc(zero)))"は1になるんだよ。 [ほのか]じゃあ、手がグーのときにpredを使うとどうなるの? [ひより]ほのかちゃん、0から何を引いても0って学校でならったじゃない。 [ほのか]そっかそっか。物凄く分かりやすいね♪ ***第2話 チャーチ様の嵐、ふたたびなの*** [ほのか]けど、どうせなら足し算や引き算もやりたいね [ひより]そう言うと思ってそっちも用意しておいたんだよ。 add = function(x) { return function(y) { return (x(suc))(y); }; }; sub = function(x) { return function(y) { return (y(pred))(x); }; }; [ほのか]今度は物凄く短くて読みやすいね。どうやって使うの? [ひより]"x+y"を求めたいときは"(add(x))(y)"って書くんだよ。 [ほのか]これは少し分かりにくいというか癖があるね。 カリー様が見てるからかな? [ひより]そうだよ。カリー様崇拝をやめればもう少し簡潔に書けるんだけどね。 [ほのか](じゃあやめればいいのに……) [ひより]"(数(計算))(初期値)"って書くと、初期値に対して計算を数と同じ回数だけするんだよ。 [ほのか]つまり、"(x(suc))(y)"はyに対して"1を足す"って計算をx回するわけだね。 [ひより]そのとおりだよ。 "(y(pred))(x)"はxに対して"1を引く"って計算をy回やるんだよ。 [ほのか]あ、これなら確かに足し算と引き算になるね。 これってなんか凄いね! [ひより]これがチャーチ様の考えた符号化の凄さだよ。 [ほのか](チャーチって英語で教会だよね。チャーチ様って神父様?) ***第3話 判定、そして条件分岐なの!*** [ほのか]ところで、単に計算をする以外にもっとすごいことできないの? [ひより]すごいことって言われても困るんだけど…… [ほのか]そうそう、二つの数が同じだったら何かをするとかいうやつできない? [ひより]じゃあ、まず二つの数が同じかどうか調べるところから説明するね。 t = function(g) { return function(u) { return g; }; }; f = function(g) { return function(u) { return u; }; }; zerop = function(x) { return (x(function(w) { return f; }))(t); }; equal = function(x) { return function(y) { return ((zerop((sub(x))(y)))(zerop((sub(y))(x))))(f); }; }; [ひより]"zerop(x)"はxが0かどうかの比較で、"(equal(x))(y)"はxとyが同じかの比較だよ。 [ほのか]equalっていうのが一つだけ見にくいね。 [ひより]xとyが同じって言うのは、"x-y"と"y-x"が二つとも0かどうか試さないといけないからね。     あとは"(数(計算))(初期値)"をうまく組み合わせてるだけだよ。 [ほのか]そっかそっか。神父様ってやっぱし凄いね♪ [ひより]じゃあ、次は一緒だったときに特別な動きをするような場合を説明するね。 cond = function(p) { return function(a) { return function(b) { return (p(a))(b); }; }; }; [ひより]これで"((cond(比較))(数1))(数2)"って書くと、比較が正しいときは数1、正しくなければ数2になるんだよ。 [ほのか]あれ? "数1"と"数2"ってなってるけど、ここに式を書いちゃ駄目なのかな? かな? [ひより]それはいい質問だよ。場合によってはいいんだけど、JavaScriptが遅延評価でない以上は、     引数をいったん関数抽象して無理やり遅延評価する方式をとるべきだよ。 [ほのか]ひよりちゃん……ごめん。 なに言ってるのか全然分からないよ。 [ひより](無視しつつ) せっかくだからそっちも書いとくね。 cond2 = function(p) { return function(a) { return function(b) { return ((p(a))(b))(p); }; }; }; [ひより]使い方は"((cond(比較))(function(w) { 計算1; }))(function(w) { 計算2; })" だよ。 [ほのか]お願い、ひよりちゃん……一人突っ走らないで…… ***最終話 新たなる力、利用なの!*** [ほのか]ところでさ、ひよりちゃん。 [ひより]どうしたの? JavaScriptに型推論がついてないのが気に入らなかったの? [ほのか]いや、そうじゃなくてさ、これって私が想像してたプログラミングと何か違うんだけど…… [ひより]え? 計算の話を聞きたかったんじゃないの? [ほのか](そういえば最初にそんなことを言ったっけ……) [ひより]あれ? プログラミングの勉強がやりたかったんだっけ? [ほのか](泣きそうな顔をしながら)  ありがとう、ひよりちゃん。 もう十分に勉強になったよ。 [ひより]でも、せっかくだから最後に再帰の勉強でもしようよ。 [ほのか](もう…どうでもいいよ……) Y = function(y) { return (function(x){ return y(function(z) { return (x(x))(z); })})(function(x){ return y(function(z) { return (x(x))(z); })}); }; wa = function(f) { return function(n) { return ((cond2(zerop(n)))(function(w) { return zero; }))(function(w) { return (add(n))(f(pred(n))); }); };}; [ひより]これはね"(Y(wa))(n)"って書くとn+(n-1)+(n-2)+…+0を計算してくれるの。 [ほのか]なんか急にすごい内容になってない? 難しすぎない? [ひより]締め切りとか締め切りとか大人の事情は関係ないもん! [ほのか](嗚呼…人間とは締め切りの前では かくも無力なモノなのだろうか…) ほのかが深い絶望に呑まれかけていたその瞬間 一際眩い閃光がλ(ちから)無き少女の体を貫いた… 「覚醒めよ…勇敢なるλを持つ者よ…  直系のλを受け継ぎし者よ…  かつて私は手続き型言語を封印せし折、λの槍を放ったが故右腕を失った…  今そのλを開放すれば、右腕はおろか全身が吹き飛ぶやも知れぬ…  御主にその覚悟があるか?  …ならば今こそ覚醒めよ<λの右腕>よ!」 [ひより]Yはカリー様の不動点演算子っていって、"Y(YF)=YF"なんだよ。 [ほのか]Curryの不動点演算子の定義と形が少し違うのは遅延評価でないからかな? [ひより]waは再帰を使いたいから、自分自身を表す変数が欲しいの。 [ほのか]それがfだよね? [ひより]そのfに値を送るのがYの役目なんだね。 [ほのか]そっか、一見難しそうだけど簡単だね♪ [ひより]ほのかちゃん、急に理解できたみたいだけど、どうしたの? [ほのか]……少し、λさまに会ってきただけだよ。 こうして、二人は関数型言語によるプログラミングを学び、 後に、ラムダ式算法騎士団に入団したとかしなかったとか。 ちなみに、著者がラムダ計算をよく理解していないといのはまた別の話。