技術記事 by 岡部 健

関数型プログラミング超入門



関数型プログラミングに目覚めた!IQ145の女子高校生の先輩から受けた特訓5日間

数年前に執筆、出版した、関数型プログラミングの入門書の冒頭ドラフト部分のみ公開します。

前ブログでも出版社の許可を得て公開していた冒頭ドラフト部分の冗長だった大部分をばっさり削り、ES6記法に修正した上で再掲しています。

基本、命令型プログラミングの根本的な問題点を指摘し、関数型で簡潔に書き直せる、ということを紹介しています。

Day1 それは「関数型プログラミング」という新世界の幕開けだった

「ずいぶんとダサいコードを書いてるのね。」
不意に背後から声をかけられ振り向いて見ると、透き通るように白い首筋が目の前にあった。
「はっ、サクラ先輩!」
スラリとした長身を前かがみにしてモニターを覗きこむように見ている長い黒髪の少女の存在をセキヤが理解するまでに一瞬の間が必要だった。放課後の電子計算機室。コンピュータ部の活動に自由に使用して良いことになっている。今日は他の部員は誰もおらず、セキヤ一人きりで黙々と作業をしていたのだが、いつのまにかコンピュータ部の部長であるサクラが様子を見に来ていたようだ。

1から10までの数をすべて足すコードを書け

サクラは無造作に近くにあった椅子を引っ張ってきて、セキヤの隣に姿勢よく座った。セキヤはサクラと寄り添うような格好になってしまった上に至近距離で自分の名前を呼ばれて著しく混乱してしまっている。
「基本的なところから始めてみましょうか。
1から10までの数をすべて足すコードを書いてみて?」
「はい。」
問題に向かえば、セキヤは不思議と心は落ち着く性分だし、なんとか書き慣れたコードをタイプしていった。

var s = 0;
for (var n = 1; n <= 10; n++) {
  s = s + n;
}
console.log(s);
55

「ほら、どうです!こたえは55。」
軽い達成感とともにセキヤは、サクラへ誇らしげに言う。
「ただ動くだけで、やっぱり超ダサいわ。」
「え?」
「仕方がないわね。私ならクールにこう書くわ。」
キーボード上に細長く美しい指が流れる光景に、しばしセキヤは見惚れてしまっていた。

「どうかしら。」
サクラの声に我に返ったセキヤ。コードをじっくりと見てみる。

const plus = (a, b) => a + b; 
const s1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
  .reduce(plus); 
console.log(s1);
55

「へ?なんだこれ?」
「クールでダサくないイケてるコードよ。」
「ダサいと言われ続ける今の僕のスキルじゃ、先輩のコードは意味がわからないし、ごちゃごちゃしてるようにも見えます。」
「生意気な。ごちゃごちゃうるさいのはあなたよ。今からそれを説明してあげるんだから、ちょっと黙れ。」
イラッとしたサクラに叱咤されてしまった。
「申し訳ありません、サクラ先輩。大変失礼いたしました!」
セキヤは半ば恍惚としてしまっていた。

フローは複雑でバグの元凶

サクラはちょっと考えてから作図アプリを立ち上げ、手際よくグラフを作成していった。

フローチャート画像

「セキヤ君、これが何かわかる?」
「もちろんです。フローチャートと呼ばれるもので、このフローチャートは僕が書いたコードの流れ、つまり僕のコードのフローをグラフ化したものです。」
サクラは満足そうに頷いた。
「正解。そして今回の問題はなんだったっけ?」
『1から10までの数をすべて足すコードを書け』でした。」
「そうね、では、このフローチャートをぱっと見て、
これが『1から10までの数をすべて足すコードを書け』の解法を示していると即座に答えられる人はいったいどの程度いると思う?」
「たしかに、わかりにくいでしょうね。でも少しは居ると思いますよ。」
セキヤの若干の反抗を感じ取り、またちょっとイラついた様子のサクラではあったが会話を続ける。
「いいわ。では仮にこれが『1から10までの数をすべて足すコードを書け』という問題ぽいな!とすぐ見抜けるほど気の利いた人がいるとしましょう。でもこのフローチャートで表されるコードが、
本当に『1から10までの数をすべて足す』の解法として合っているのかどうか?確信をもって断言できる人はどのくらい居るのかしら?」
「それは
まず居ない
と言って良いんじゃないでしょうか。コードにすぐバグが紛れ込む事はプログラマならば誰でも身を削るようにしてよく理解しているはずです。実際に実行もせずにコードが正確かどうか確信をもって断言できると思ってるプログラマが居るとしたら、そいつはモグリでしょう。」
サクラは再び満足気に頷いた。
「よくわかってるじゃない。つまりセキヤ君のコードはかんたんなようで実はかなり複雑なのよ。コードの妥当性を検証するためには、頭の中でフローチャートに従って順番に変数の値を追っかけて何度も確かめてみたり、手っ取り早いのは実際にコンピュータでコードを走らせてみてエラーが出るか試してみる事でしょうね。そのほうが速くて正確。でも、エラーが出ないバグが一番やっかいね。だから最終的には変数をウォッチするデバッグ機能なども利用して変数の値を逐次検証していく必要はある。」
「おっしゃるとおり、コードのフローを逐一追跡しながら隠れたバグを探し出すのはものすごい骨の折れる作業ですよね。よくわかります。」
「そして、これこそが多くのコードが抱える根本的な問題なの。バグを解決できずにプロジェクトそのものが頓挫することはままあるわね。セキヤ君のコードがダサいと言われる理由もそこなの。フローは複雑でバグの元凶なのよ。

フローは不要

「先輩の言うことはもちろんよくわかるんです。でもプログラミングって元々そういうものなんじゃないんですか?フローがあってこそのコードですよね?」
「だから、その発想がダサいのよ。」
うんざりとした調子でサクラが言う。
「そうなのかな?」
『1から10までの数をすべて足す』という問題には、繰り返しや条件判断といったフローはある?」
『1から10までの数をすべて足す』という問題自体には、繰り返しや条件判断といったフローはまったく確認できない
ですね。」
「では何故、セキヤ君のコードにはそんな複雑なフローの存在が確認できるのかしら?」
「それしか方法がないからですよ。この問題を解くには、繰り返しや条件判断といったフローを含むコードを書くしかない。フローは絶対に避けられないと思います。」
「ほんとうにそう?私のクールなコードはどうかしら?」

const plus = (a, b) => a + b; 
const s1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
  .reduce(plus); 
console.log(s1);

「うーん、言われてみればフローが消えてるなあ。僕のコードにあったforループの繰り返しや条件判断がすべて綺麗さっぱり消えてなくなっています。
「いい?フローをもってコードを書くことは唯一の方法ではないの。むしろフローを書くことは複雑な仕事でバグの温床となるダサいやり方なので、フローは極力さけるべきなの。」
「なるほど。」
「**『1から10までの数をすべて足す』**という問題にフローはない。ならば、コードもそのままフローなしに書ければいいと思わない?」
「それが出来るならば言うこと無いです。コードはクールになりますね。」
「クールなコードでは、フローの設計やフローの妥当性の検証に労力を費やすようなダサい真似はしないのよ。」
「つまり、プログラミングの最大の課題であるデバッグの手間が激減するわけですか?それはかなりクールなことになってしまう。」
「わかってきたわね。」
フローじゃなくて不要(フヨー)ですね。
「ダサいダジャレだけど、まさにそのとおりよ。」
口元をゆるませながらサクラは続ける。
「でもフローが不要という引き算だけでは、私のコードの真価を理解しているとはとても言えないわね。」
「というと?どういうことでしょうか?」

フローを書かず論理をそのままコードに書き写せ

「フローがない、フローの設計が必要ないということは、問題の論理だけに集中している、ってことに他ならないの。ここ重要。セキヤ君は、プログラミングで後々どうせ膨大な検証が必要となるフローの設計に時間を費やしたいかしら?それとも問題の論理だけに時間を費やしたいかしら?」
「それはもちろん、問題の論理そのものだけに集中してコードを書いていきたいです。」
「そうでしょう。私のコードのクールさの真価はそこにあるの。問題の論理そのものに集中しているの。」
「もう少し説明をお願いします。」
「 *『1から10までの数をすべて足す』**という問題について、私のクールなコードでは、

const plus = (a, b) => a + b; //足し算
const s1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] //1から10までの数を用意して
  .reduce(plus); //すべて 足す
console.log(s1);

と、問題の論理そのものを単純にコードへ、まる写しにしているだけなの。」
「あ!?なんで?凄い・・・先輩、やっと見えてきました!」
「わかった?私のクールなコードでキモとなる部分はたったこれだけ。問題の論理そのものを書き写しただけなんだから、バグが介入する余地なんて最初からどこにもあるはずがないでしょ?」
「まったくないです。」
「ということは、つまり、私は本当に『1から10までの数をすべて足すコードを書け』の解法として合っていると確信をもって断言できるわけ。
プログラミングの世界で、論理のみで構成された、これ以上望めないほど見透しの良いクールなコードを書くことは、ダサいコードを平気で書く常人プログラマーの想像を超えた『コードを見透せる眼』をもつことに等しいのよ。」

関数 ・function という論理操作

「 先輩、

 .reduce(plus); //をすべて足す

ここなんですが、reduceとplusも2つともJavaScriptでいう関数ですよね?」
「そうね、関数について説明しておいたほうがいいわね。」
「僕はざっくりとしか理解してないので、おねがいします。」
関数というのは中学校の数学の授業でも習うとおり、もともとは数学用語なの。それがそのままプログラミング用語に転用されたものね。

関数は論理の最小単位の部品として最上の扱いを受ける

「先輩、

 .reduce(plus); //をすべて足す

で、 reduceplusという2つの関数が組あわされて『//をすべて足す』という関数になってしまっているのでしょうか?」
「まさにそのとおりよ。
『//をすべてxx』と『足す』という2つの関数が組み合わさっているの。」
「2つ関数が組み合わさって、『//をすべて足す』という関数になるんですね。」
問題の論理そのものをコードに単純に書き写すことを徹底的にやっていくためには、あらゆるものがレゴブロックのように組み合わされることが超重要。
関数は、レゴブロックのような最小単位の部品なのよ。論理の最小単位としての部品。
「先輩のコードをよく調べてみると、

 .reduce(plus); //をすべて足す

のうちplusという関数は、

const plus = (a, b) => a + b; //足し算

という形で『足す』という論理の最小単位で部品として独立しているんですね。」
「そうね。論理の最小単位を、きっちりと設計してやるの。そうやって仕上がった関数は、レゴブロックのように自由自在に取り回せるし、レゴブロックのように自由自在に組み合わせることができるわ。こういう部品は、第一級オブジェクト(first-class object)ってクールに呼ばれているわね。」
「というと、JavaScriptの関数はファーストクラスのオブジェクトですか?クールですね。」
「ええ、JavaScriptの関数はゴリゴリのファーストクラスよ。ファーストクラスな部品である関数は、ファーストクラスなので第一級、最上級のクールな扱いを受ける
『足す』という論理操作は、それ自体が最小単位として独立しており、きちんとファーストクラスの最上級の扱いを受ける資格がある
のよ。」
「関数がファーストクラスかどうか?っていうのはプログラミングではかなり重要なスペックなんですね。今後プログラミング言語を調べるときには注意してみます。」
FUNCTION・関数は論理の最小単位の部品で最上の扱いを受ける。ここはクールなコードにとって重要なところよ。」

「『様々な問題を抱えた家』があるわよね?
『玄関の専門的スキルをもつ匠』を設計する。
『台所の専門的スキルをもつ匠』を設計する。
『階段の専門的スキルをもつ匠』を設計する。
それぞれの専門分野に特化した匠を論理的に自在に組み上げて、最終的に『たったひとりの匠』に仕上げる。この彼こそが『様々な問題を抱えた家』の『様々な問題』を一挙に解決する能力をもつ『万能のスーパー匠』なのよ。
ここまですべて論理操作の構成しかやっていないの。
問題の論理そのものをコードに単純に書き写すことを徹底的にやっていくわけ。そして、『万能のスーパー匠』を組み上げることこそが問題の論理であり、それはそのまま問題の解決になるの。 それぞれ逐一実行しろ、という計算の手順書ではないわ。2つの関数を組み合わせて、『//をすべて足す』といういう**『スーパー匠』を設計**してやっているのよ。」

論理操作の組み合わせの設計だけで、Afterという結果や、途中の計算手順も、フローも、変化し続ける状態変数も何ひとつないんですね。」
「そう。**手続きを分割して結果を小出しにするループのフローを書くのではなく、論理を分割し論理だけを構成しなさい。**いいわね?」
「はい。よくわかりました。」

『まとまり』は美しい単一の論理構造

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 1から10までの数というのはひとつの『まとまり』なわけ。この『まとまり』をまるごと処理ができる関数が、実は.reduceなの。これを見て気がついたことを言ってみて。」
「同じ関数でも.(ドット)が頭についていますね。それからplusを引数として取り入れているようです。」
「はい、まず.(ドット)が頭についていることについて説明してあげるわ。」
「お願いします。」
「そもそも、この.(ドット)は、[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] という配列に紐付けられているわけね。
JavaScriptの配列には、reduceというファーストクラスな関数が、
最初から装備されているということなの。
つまり、このreduceはJavaScriptの配列お抱えの論理操作の最小単位なのよ。配列はぜひ、この論理で操作してください、と周到に前もって準備がなされているのね。」

「先輩、なぜそのような周到な準備がなされているのでしょうか?」
「なぜなら、配列というものが『まとまり』だからよ。配列という『まとまり』をまるごと処理ができる関数が用意周到に準備されて提供されているの。」
「あと、plusは普通に『足す』とわかったんですが、reduceという言葉の意味が不明です。普通に訳せば減らすですよね?」
「ああこれは、むしろ数学用語なの。通分する、約する、方程式を解くという意味で使われるわ。今回のケースでは、[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]をすべて足すと55というように、10個のデータが計算されてひとつに集約されるじゃない。だいたいそういう操作のイメージよ。」

「なるほど、そう言われてみると合点がいきますね。」

他の関数を取り扱う能力をもつ関数

「次に、 .reduce(plus); //をすべて足すというように、plusを引数として取り入れていることについて説明。さっきみてきた通り、plusという関数は『足す』という論理の操作で、ファーストクラスの最上級の扱いだったわよね?」
「はい、論理の最小単位として自由に取り回せます。」
reduceは、配列という『まとまり』をまるごと操作できる関数だけど、同時にこういう他の、plusみたいな別の関数を取り扱う能力をもった特別な関数なのよ。」
「なるほど。だから組み合わせができたんですね。」
「こういう他の関数の取り扱う能力を備えた関数のことを特に**高階関数(higher-order function)**と呼ぶわ。ほんとは名前なんてどうでもいいのだけど、他の人たちもそう呼ぶので、話を合わせるために一応知っておいたほうがいいわね。」
「JavaScriptのreduceは高階関数である、と言われても今後凹むことはなさそうです。役立ちます。」
「プログラミング言語って世界共通語といえるけど、高階関数っていう名前は日本語ローカルだからどうせ外国では意味が通じないの。だからといってhigher-order functionって覚えたら今度は日本国内では通じにくいし所詮その程度のものよ。」
「こういう専門用語は日本語・英語どっちで揃えるかややこしいですね。」

高階関数の柔軟性

「でも、高階関数の存在価値って何?こんなものが世の中に存在している価値ってあるのかしら?」
「もちろんです、先輩。専門的匠の集団から万能のスーパー匠を組み上げる際などには必要不可欠だと思われます。」
「まったくそのとおりね。今回のかんたんな問題でもっと具体的に論じるとどうなる?」
「今回の場合で言うなら、reduceは、『//をすべてXXする』という、まとまりへの操作として事前準備されているんですよね?XXというのは、この高階関数が引き受ける別の関数に応じて変化して、今回は『足す』だったので、組み合わせで、『//をすべて足す』でに仕上がっていたはず!」
「だから、どういう存在価値?」
まとまりへの操作っていうのは、いろんなケースがありえます。だからそのやりたい操作に応じて柔軟に関数を渡せばいいんじゃないでしょうか?」
「セキヤ君もなかなかやるじゃない。じゃあ『すべて足す』のではなく『すべて掛ける』場合はどうすればいいのかしら?」
『足す』能力を備えた匠を、今度は『掛ける』能力を備えた匠に入れ替えたらいいんです。だから、『掛ける』能力を備える匠をまず設計してやれば良い
と思います。」
「じゃあやってみれば?」
「多分、余裕です!」
セキヤは意気込んでタイプしはじめた。

const multiply = (a, b) => a * b; //掛け算
const s2 = [1, 2, 3, 4, 5] //1から5までの数
  .reduce(multiply); //をすべて かける
console.log(s2);
120

「いかがでしょうか?」
「合格。」

ブログ移転



同じBloggerですが、移転しました。

満足できるシンプルなデザインでモバイルでもリアクティブに、というテンプレートがなく放置していたところ、
自前でゼロから構築するやり方

Make A Blank Template / HTML Page In Blogger
https://subinsb.com/make-a-blank-blogger-template/

を発見し、

BootStrap
https://getbootstrap.com
を使って、上手く行ったので今後ここで執筆していきたいと思います。