紙箱

覚えたことをため込んでいく

Clojureと「Simple Made Easy」

プログラミング言語というのは、その作者が理想とする世界に合うようにデザインされているものだから(みんな信じないかもだけど、Javaですらそうなのですよ)、Clojureのことを理解するには、作者であるRich Hickeyのプログラミング観を知るのが手っ取り早いでしょう。

Rich Hickeyはさまざまなプレゼンテーションを発表していて、多くはネットで見られます。示唆に富んで皮肉も効いてておもしろいので、ファンも多くて、彼の独特の髪型(往年のロック歌手風)からか、「Rich Hickey’s Greatest Hits」というブログ記事もあったりします(プレゼンテーション動画へのリンク集です)。

ただ、彼のプレゼンテーションは難解な英語も出てきて、私のようなリスニング苦手人間には音声だけで聴くのは難しいです。そういう人は国外でも多いからか、有志が書きおこし(transcript)を公開していたりします。ですので、私は彼のプレゼンを、聴いたのではなく、読んだわけです。ただ動画でなにが起きてるのかわからないと文章だけでは意味が分からないところ(特に現場のジョークとか)があるので、動画も目を通すことをおすすめします。

彼の有名なプレゼンテーションに「Simple Made Easy」というものがあります。具体的なコーディングについてのプレゼンというよりは、シンプルさとは何か?を語ったものです。彼のプレゼンテーションには、そういう、実際のコーディングではなく、プログラミングの土台となる考え方や態度についてのものもあって、なかなか面白いのです。彼の哲学を語ったものとも言え、その考え方は、当然Clojureにも反映されているのだと思います。

Simple Made Easyの内容は、一度、株式会社ユーザベースでプレゼンテーションをする機会があったときに資料にまとめたことがあります。今回は、その資料をもとに、どういう内容のプレゼンテーションだったのか紹介したいと思います。ただ、これは「翻訳」ではなくて私の理解した内容の紹介であることに注意してください。Rich Hickeyが具体的にどう言っているのかは、彼のプレゼンを直接見てください。

シンプルと簡単は違う

世の中でシンプルだと言われるものの中には、実際にはシンプルではなく、単に「簡単」であるものがたくさん混ざってます。このツールを使えばコマンド一発でサーバが構築できてすごいシンプルだ!などなど。もちろん簡単なことはそれ自体価値のあることなのだろうけども(少なくともただ難しいよりは)、シンプルと簡単を混同するのは良くない。それらを混同するから、世の中には、(使うのは)簡単だけど猛烈に複雑なものが現れてくるわけです。

シンプルと簡単は違う。

簡単に使えるけども、ものすごく複雑なものというのは、たくさんあります。お気に入りのIDEはどうですか。JSならば、webpackはどうでしょうか? Spring Bootを使えばJavaで簡単にAPIサーバが作れますが、Spring Bootはシンプルでしょうか?

シンプルと簡単との違いを明確にするために、Richは、ここでいう「簡単」とはどういうものかを紐解いていきます。彼が挙げた、人が「簡単」なものに抱いているイメージは、次のようなものです。

  • 慣れている
  • すぐに使い始められる
  • 似たようなものをすでに知ってて、身近だ
  • 今の自分の能力の範疇内だ

「簡単」というのは、だいたいが「ほかと比べて」という比較が入るのです。慣れているにせよ、身近であるにせよ、「今の自分の知ってる何かと比べて」という比較なのです。

であるから、みなが「簡単」なものだけを選択していたら、誰も新しいことを始めることはできません。新しいことは、身近でもないし、慣れてもいないし、たいていは今の能力の範疇外だから。

シンプルとは

一方でシンプルというと、ぱっと「たくさんじゃなくてひとつだけ」だとか「そのツールは機能が少なくてシンプルだ」とか「このプログラムは構成要素が少なくてシンプルだ」とか、なんだか(ものなり機能なりの)数が少ない方がシンプルだ、という話になりがちです。ところが、実際には、ものごとをシンプルにすると、要素の数は多くなっていきます。

ClojureLISP系の言語ですが、たとえば、伝統的なLISPにおける括弧は、シンプルでしょうか。LISPは括弧だけで構成されているから、シンプルなのでしょうか。RichはLISPの括弧はシンプルではないといいます。なぜならLISPの括弧には、複数の意味があるからです。

LISPでは、括弧は「関数呼び出し」と「ものごとをグルーピングする」という二つの意味があります。let式でペアを表現するためのタプルとしての括弧、関数呼び出しとしての括弧、です。

つまりRichのいう「シンプル」というのは、「ひとつのものに、複数のことがらを混ぜ込まない」ということなのです。LISPの括弧には、ふたつの事柄が混ぜ込まれているので、シンプルではないと。

シンプルというのは、まっすぐの糸のようなもので、シンプルでないものというのは、糸が絡まっている状態です。「関数呼び出し」と「グルーピング」という2本のひもが、絡み合って、括弧となっているのが、LISPの括弧なわけです。

だから、「シンプルさ」というのは「オブジェクトが小さい実装である」とか「オブジェクトの詳細がちゃんと隠蔽されている」とかいう話とも関係がありません。これらはコーディング上では大事なプラクティスであるけど、「シンプルかどうか」という話とは、レイヤーの違う話なのです。

「シンプルでなくなる」という言葉を決めよう

何かを具体的につかむためには、名前をつけるのが大事です。「シンプル」のほうには名前がついてるので、その対義語の方に名前がほしい。もちろん、名詞なら「複雑(complication)」といえばいいかもしれないです。でもできれば、シンプルでないものを作ろうとしてるときに「おい、それは○○してる(シンプルじゃなくしてる)ぞ」と言えれば、シンプルさを保つ役に立ちそうです。

そこでRichは「コンプレクト(complect)」という英語(自動詞)を提案しています。何かがコンプレクトしてるから、そいつは複雑になるというわけです。コンプレクトとは、なにか複数のものが、絡み合い、もう分離できないくらいに結びついてしまうことです。

コンプレクトしてるものを探そう

言葉ができると、これはコンプレクトしてるぞ、ということができるようになります。プログラミングの世界には、身近にあってコンプレクトしているものがたくさんあります。RichはSimple Made Easyの中で、たくさんの「コンプレクトしてる」例を挙げています。

対象 何がコンプレクトしている?
状態(state) 状態を変更するすべてのもの
オブジェクト 状態と一意性と値
メソッド 関数と状態と名前空間
継承 複数の型
変数 状態と時間
アクター 「何を」と「誰が」
switch文/match文 「何を」と「誰が」とのペアが複数個混ざっている

私はこのリストを見たときに、なるほど、変数というのは「状態」と「時間」がコンプレクトしてるのか、なるほど!と思いました。言葉を定義すると、いろんなものが、簡単に表せるようになり、理解しやすくなります。

ものごとを絡み合わせない

コンプレクトしているものは、複数の要素が、もはや切り離せないレベルで結合してしまっています。この、切り離せない、という事実が、複雑さの現れるところなのです。

シンプルなものは違います。シンプルなものは、組み合わせることができます。組み合わせることで、コンプレクトしたものと同じこと実現できるでしょう。さらに、簡単に分離できます。だから合体させたり、分離してカスタマイズしたりが簡単にできます。シンプルなものは、絡み合っていないから、「簡単に」変更できるのです。

シンプルさが、簡単さを作るのです。

同じ城を作るにしても…

この図を見てください。これは実際に「Simple Made Easy」で使われた写真です。左側は毛糸細工で、毛糸を編んだり縫ったりして作る城です。右側はLEGOブロックで作った城です。

f:id:t_yano:20170515114854p:plain

毛糸細工は、パターンがわかっている場合、織り機を使えば簡単にできて面白いらしいです。馴染みがないなら、プラモデルとかに置き換えて考えるといいかもしれません。

毛糸の城と、LEGOブロックの城とでは、ツールがある前提に立てば、どちらも簡単に作れるものです。でも、一度作った後に変更しようと思ったら別です。これは重要な視点です。次の文言をじっくり考えてみてください。

テストスイートとリファクタリングツールがあれば、毛糸の城をLEGOブロックの城よりも簡単に変更できるんでしょうか?

強力なリファクタリングツールは複雑なものを安全に改変することをサポートしてくれますが、それは、LEGOブロックのような構造のプログラムと比べてどうなんでしょうか?あるいは、LEGOブロックのような構造のプログラムにリファクタリングツールを使った場合と比べてどうなんでしょうか?

テストスイートとリファクタリングツールは、あくまでも「ガードレール」であって、ガードレールなしでも簡単に変更できる、もともとシンプルなものの代用にはなり得ないんです。

「シンプルであること」は、あなたの選択だ

私たちには、複雑性の文化があります。放っておくと、なんでもこんがらがり、からみあい、コンプレクトしていくのです。そんな世界の中では、シンプルというのは、意図的に選ぶ選択なのです。

もし、シンプルなシステムが持ててないなら、シンプルであることを選択しなかった自分のせいになるのです。

テストや型システム、強力なリファクタリングは、安全性を高めてくれるでしょう。しかし、これらは強力なガードレールではあっても、シンプルさを保証してはくれません。シンプルさと、ものごとがコンプレクトしていくことの問題を解決してはくれません。だから、シンプルさというのは、常に自分の選択なんだ、とRichはプレゼンテーションで主張しています。

私たちはすぐにコンプレクトしてしまうので、常に「コンプレクトレーダー」を動かして監視しなくちゃいけない。 「コンプレクト」というワードを定義したこと自体が、そのレーダーとして役に立つはずです。

プログラムをシンプルにするには抽象化しなければいけない

プログラムやシステム全体をシンプルにするためには、ものごとを、コンプレクトしない形で結合したり分離したりできるようにしなければいけません。あっちとこっちの共通項を決めて、LEGOブロックのように、つなげたりはずしたりできなければいけません。そのためには、物事を抽象化しなければいけません。

抽象化というのは、「複雑さを隠す」ことではありません。

ものごとから、実装の詳細を取り除くことです。

そうすることで、ものごとは組み合わせ可能になって、たくさんのものを、共通の方法で扱えるようになります。LEGOブロックになるのです。

RDBは、「集合」という抽象化を使っています。であるから、テーブルやサブクエリも「集合」として共通の扱いができ、同じようにJOINしたりWHEREでフィルタしたりできます。

Clojureは、言語を貫く共通の抽象データ構造を定義していて、マップであれrecordであれ、あるいは独自に定義した何らかの型であれ、Associativeとして扱うことで、さまざまなものを共通の方法で扱えるようにしています。

データに抽象構造を見いだして、すべてをそこに集約していく必要が出てきます。LEGOブロック化するわけです。それはインタフェースを定義することかもしれないし、もっと大きなシステムであれば、サーバー間の関係をどう捉えるか?まで広がるかもしれません。

シンプルは選択だから、シンプルなシステムがほしいのであれば、システム全体を、コンプレクトしていない単位に分割できるか考えていかねばなりません。

そうしてコンプレクトしていない単位になり、抽象構造で接続されたシステムは、おそらくは「簡単」に変更できるでしょう。

まさしく Simple Made Easy なわけです。

とはいえ、現実の世界で「完全にシンプル」というものの実現は難しかったりもします。Clojureだって、グループ化としての括弧にはベクタなりマップなりを使うことで、丸括弧はおおむね「関数呼び出し」と見なしても大丈夫ですが、丸括弧をリストとして使えないわけではありませんし(でないとマクロを書けないので)。

だから現実にはトレードオフが存在するでしょうし、そのトレードオフこそが、選択なわけです。プログラムやシステムをシンプルにするのは、いつも、私たちの選択なんです。

この「Simple Made Easy」というプレゼンテーションで、RichはClojureの話をほとんど出さないので、具体的にどの部分に影響が、とかは語られてません。しかし、Clojureの並列化ライブラリ core.async を使って作ったプログラムが、「関数」と「チャネル」というそれぞれシンプルな構造を、transducerやpipelineを使って組み合わせる形で作られるところにも、この発想が現れているんだと私は思っています。

シンプルにすることはチャレンジでもあるので、うまくいったものやそうでないものもあるでしょうが、この考え方は、Clojureのいろんな部分に息づいているように思います。

今回は、「Simple Made Easy」というRich Hickeyのプレゼンテーションを通して、Clojureにおけるシンプルさの考え方について書いてみました。

次に何書くかは未定です。。