読者です 読者をやめる 読者になる 読者になる

紙箱

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

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におけるシンプルさの考え方について書いてみました。

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

Clojureの世界観

ブログを書くのは久々です。
京都で小さな会社をやっていて、自社開発でClojureとClojureScriptを使用し続けて、概ね3年くらい使い続けています。その過程で、Clojure自体にも小さいながらソースレベルの貢献ができたりして、オープンソースプロジェクトとしても面白かったのですが、もともとオブジェクト指向言語ばかりやってきたところから、Clojureという、まったくオブジェクト指向言語ではない言語に飛び込んだ経験や考えたことなんかを、ブログにストックすると、何か他の人にも役立つこともあるかと思って、ブログに書くことにしました。

このところずっと、自社の仕事とは別に、恵比寿にある 株式会社ユーザベース さんのお仕事に参加しています(私が法人を作る前からなので、もう5、6年くらいになります)。そちらの方でもClojureやシステム設計の話(プレゼンなど)などを何度かさせてもらったり、ここ半年くらいは、ユーザベースさんでもClojureを実開発へ投入し始めたため、開発支援として携わりました(Clojureのシステムは現在も鋭意開発中です。ぶっちゃけClojure開発者募集中です)。
ユーザベースさんのシステムにおいても、Clojureでgo-blockを実現する並行処理ライブラリ core.async を使って並行処理システムを作ったりしているので、そこで得られた、Clojure+core.asyncでの並行プログラミングの知見なんかも書いていきたく、これからしばらく、不定期でClojureについてブログを書いていくつもりです。気力が続けば。

今回は、Clojureを使うことによって、Clojureが前提としている世界観が、自分が空気のように前提としていたものと異なってるところとして、Clojureにおける抽象データ構造と、ポリモフィズムの仕組みについて書いてみます。

少ない抽象データ構造と、たくさんの関数

Clojureでは、オブジェクト指向言語と異なり、関数とデータはおおよそ完全に分離しています。オブジェクト指向言語でないのだからそうなのだろうってことは皆、知ってはいるのだろうけども、関数(メソッド)をデータと結びつけて考えてしまうというのは、オブジェクト指向言語に慣れた人のみたいなものとして、染みついているところのようにも思います。

Java的な、クラスベースのオブジェクト指向言語の場合、メソッドを作るにはクラスを定義してそこにメソッドを足すわけなので、メソッド(関数)はあるデータの集まり(オブジェクト)を操作する専用の関数として定義しがちです。そりゃ、「そのオブジェクトを操作するためのメソッド」なのだから当たり前です。

一方、データと関数が分離している場合は、関数を特定のクラスやオブジェクトに結びつける意味はないですし、逆に、ある関数のために専用のクラスを作る意味もない。極端に考えれば、すべての関数が共通のデータ構造を処理するようにした方が、多様な関数を柔軟に活用できるようになる。

というよりも、オブジェクトに関数が結びついているからこそ「このメソッドはこのオブジェクト構造を処理するためのものだ(他の用途には使えない)」という風に専門化できていたんであって、データと関数を個別のものと扱う以上は、「この関数はこのオブジェクトだけを扱う」という前提を置けないのです(当たり前です)。あるオブジェクトと別のオブジェクトが、型であったりクラスであったりが異なったとしても、関数は、そのオブジェクトが、関数の処理できる構造であれば、処理できるべきなんです。関数とオブジェクトが独立しているというのはそういう意味であるべきです。であれば、すべての関数にまたがるような、共通の汎用データ構造があって、すべてのデータはその汎用性を担保してたほうがいい。

もちろん、「縦」と「横」という情報を持ったデータを使って「面積」を求める関数は、データが「縦」と「横」という情報を持っていることを求めるだろうけども、RectangleだとかTableだとかの特定のクラスにダイレクトに結びつくべきではない。

この課題にはいろんなアプローチがあるんでしょうが(Structural Typeとか?)、Clojureの場合は、言語全体が前提とする抽象データ構造があります。リスト状のものはSeqと呼ばれる抽象データ(代表例は配列)として、マップ状のものはAssociativeと呼ばれる抽象データ(代表例はマップ)として扱います。事実上ほとんどのデータがこの2つで表現されていて、多くの関数が、引数としてSeqまたはAssociativeを受け取る(あるいは内部で自動で変換する)し、SeqまたはAssociativeを出力します。
自分が関数を書くときにも、独自のデータ型ではなく、SeqかAssociativeを前提として書くのがオススメです。そうすることで、関数は他の関数のインプットになりえますし、他の関数の出力を入力できます(各関数の入力と出力が同じ抽象データ構造だから)。

f:id:t_yano:20170409211658p:plain

この抽象化は徹底していて、例えば、Clojureにはdefrecordがあり、これを使えば、 レコードと呼ばれる、独自のとフィールドを持ったデータ構造を作り出すことができます。さらに、レコードに対してプロトコルを実装することで、Javaにおけるインターフェースを実装するような感じのコードを書くこともできます。

(defrecord User [name age mail-address]
  IAuthenticate
  (auth [this param] (something-great param)))

これだけ見ると、まるでクラスを定義できるかのように感じます。ところがこのレコードはすべてAssociativeでもあります。つまり、すべてのレコードはAssociativeを処理する関数に渡すことが可能です。また、このデータを使う側は、データが実はレコードであるということを気にする必要も(原則としては)ありません。それはAssociativeだということさえわかればいい。実際、あるライブラリのある関数の戻り値が、あるバージョンまでマップ(典型的なAssociativeデータ)であったものが、次のバージョンからレコードに変わっていたとしても、特に影響はないはずです。

;; マップ
(def data1 {:name "t_yano"})

;; レコード
(defrecord User [name])
(def data2 (->User "t_yano"))

;; どちらもAssociativeなので、:name関数で:nameの値を取り出せる
(:name data1) ;=> "t_yano"
(:name data2) ;=> "t_yano"

このような構造はJavaのインタフェースのようなものがあれば、他でも実現できると思うだろうけども(実際、Associativeと Seqは、Javaのインタフェースとして定義されています)、それは「注意深く設計すればそのライブラリ内ではそうできる」という話であって、言語として標準の抽象データを定義して、すべてをそこに集約させよう、という世界では、他の言語機能が、すべて、このような仕組みを支援するように作られています。言語としての前提であるからです。たとえば、JavaですべてのデータをMapで作っても、苦しいだけで利点などないでしょう。そういう前提で言語が作られていないからです。

このような抽象データ構造があるからこそ、関数とデータを分離できるわけです。関数は共通の抽象データ構造しか見ていないからこそ、実際のクラスとは結びつく必要がないのです。

「10種類のデータ構造にそれぞれを扱う10個の関数があるよりも、ひとつのデータ構造を扱う100個の関数がある方が良い (It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures)」という言葉あります。

データ構造にそれぞれ関数がある

一つのデータ構造を扱う100個の関数

この一文に賛成の人も反対の人もいるでしょうが、Clojureは明確に「ひとつのデータ構造を扱う100個の関数がある」ような世界の方がいい、という前提で作られているのです。

アドホックな多態(ポリモフィズム

さて、少ない抽象データ構造とたくさんの関数、という理屈はわかったとして、オブジェクトに結びついてない関数における、多態(ポリモフィズム)はどうなるんでしょうか。用途ごとに異なる関数を用意すればいいってことなんでしょうか。
もちろん、実際の開発には多態は必要です。多態なしで、MySQLPostgreSQLで異なる動きをするconnect関数をどうやって定義したらいいんでしょう。 mysql-connect と psql-connect を使い分ける、というようなことが避けたいところです。

ですから、Clojureにも多態関数を定義する仕組みがあります。defrecordによって独自の型を定義し、引数の型に関数実装を紐づけることもできます。あるいはdefmulti / defmethod を使ってマルチメソッドを定義することで、データの(実際には式の結果ですが)によって関数実装を切り替えることもできます。例えば、引数として渡されたDB接続定義のドライバ名に基づいて、connect関数の実装を切り替える、といった使い方もできます。

関数(メソッド)を多態にする仕組みについてはJavaScriptみたいなアプローチや、引数の型の組み合わせによって切り替えるマルチプルディスパッチがあったり、世の中にはすでに色々なものがあるので、Clojureの多態の仕組みがそれほど独自のものだというわけではないでしょう。強いていえば、Clojureのマルチメソッドで追加した関数は、動的に紐付けを追加したり切り離したりできるのが面白いところですが、それ自体も、他の言語(特に動的な言語)でできないというものでもありません。

多態を実現する仕組みよりも、Clojureの、多態に対する態度というか、考え方の方が、面白いのではないかと思います。
Clojureでは、Clojureの多態の仕組みのことをAd-hoc Polymophismと呼んでます。このワード自体は(Clojureよりも前に)結構昔からあるようなので、新しい概念ではないのでしょうが、それによる実際の開発への影響が結構面白い。

多態(ポリモフィズム)というのは、オブジェクト指向の世界では、「あるオブジェクトと別のオブジェクトが同じメソッドを持っているけども違う動作をする」とか、逆の視点から、「あるオブジェクトと別のオブジェクトは、実際には違う動作をするだろうが、同じメッセージに反応するので同じオブジェクトとみなせる」といった文脈で使われる感じがします。オブジェクト指向のメッセージ・パッシングの考え方にのっとれば、あるオブジェクトAとBに同じメッセージを送っても、実際に動く処理(メソッド)は別かもしれない、だが同じメッセージに応答できるのだから、両者は同じオブジェクトとみなせる、というわけです。
つまり、いつもオブジェクトとともに、オブジェクト同士の関係(同じメッセージに応答するのだから実質同じとして扱える、とか、サブタイプである、とか)として語られる傾向があります。

一方、Clojureにとって多態とは、関数ディスパッチの話と捉えられています。関数はデータ(オブジェクト)とダイレクトには結びついてないので、多態の説明として、オブジェクトやクラスと結びつけて語ることはできません。ある関数を呼んだ時に、実際に実行される関数実装はどれなのか?というディスパッチの問題に過ぎないのです。

ディスパッチの問題、ということは、実際のところ、関数を使う側にとっては、実は関数が多態であるかどうかということは、使用上あまり関係がないということです。関数は関数であって、それが多態であるかはユーザーには関係がない。
例えば、ただの関数は、その関数を呼ぶと関数本体に直接ディスパッチされます。これも関数ディスパッチのひとつの形です。
これがマルチメソッドであれば、引数の値(実際には値を計算した結果)によって実際に使う関数実装が探された後に呼び出されます。defrecord / defprotocol によるディスパッチでは、引数の型によってディスパッチされます。それは実装の詳細であって、関数を使う側にとっては、関数を呼べば関数が実行されればいいのです。

もしある別の人の作った関数実装が、内部でプログラムとしてif文を使って別の関数に処理をディスパッチしていたとしても、使う側にとっては関係ないのと同じです。if文での分岐は、手動の関数ディスパッチと考えれば、マルチメソッドと同じく関数ディスパッチだと言えますし。

使う側にとってはいずれにせよただの関数に見えるのですから、Clojureにおいて、関数はあとでいつでも多態に切り替えられる存在なわけです。

過去に私がDB操作ライブラリの一つ Korma に送ったパッチでは、列名をクオートする処理をMySQLPostgreSQLとで分ける必要に対応しました。対応は簡単で、クオートする関数をマルチメソッドに変更し、引数として渡ってくるDB接続定義の接続URLを使って適切なクオート処理にディスパッチするだけです。関数がマルチメソッドに変わったわけですが、関数を呼び出す側から見ると、やはりただの関数に見えますので、他の部分にはまったく影響しません。

つまり、コードを書いている時に、ある関数を多態にするかどうかというのは後から考えても大丈夫な仕組みなわけです。もちろん、作ってる段階で、設計として「ここの関数は外から拡張できるようにしたいから、マルチメソッドにしておこう」とか「ここはユーザー利便性を考えてプロトコルを定義しておくか」ということはありますが、そうでないところを後からマルチメソッドやプロトコル関数化することも、結構簡単に行えるのです。

だからAd-hoc(場当たり的な)ポリモフィズムと呼ぶわけです。もちろん注意すべきことはあって、対象の処理が関数に分離されてなければ、後で多態関数化することもできませんから、できれば関数は細かく分けておいた方が、後から対処しやすいと思います。そのような注意点さえクリアしておけば、柔軟に後からコード変更可能だ、というのも、Clojureの利点の一つです。

他の文化を無理やり持ち込まないの大事

プログラミング言語には、言語ごとに文化というか、大事にしている考え方があるものです。ClojureにはClojureの大事にしているものがあって、言語仕様自体が、その大事にしているものを前提に設計されているはずです。上に紹介したものも、Clojureという言語に意図的に組み込まれた仕様な訳です。

他の言語から新しい言語に移ってくると、最初は、今まで馴染んでいた言語の文化と、新しい言語のやり方とが、まったく異なっていることに混乱して、自分の馴染んでいる文化と同じにしようとしてしまいがちです。しかし、言語仕様とその言語の文化は強くつながってるものなので、文化を無視して別の文化で書こうとしても苦しいだけです。

そのためには、やはり、その言語が大事にしているものはなんなのか、どういう意図で、どういうことを実現したくてそういう言語デザインになっているのか、を知るのが大事だと思うのです。意図がわかれば、馴染めなかったものにも急に「なるほど、そういうことか」と納得感が得られるものです。抽象データ構造や多態の仕組みなんかも、そのような例の一つです。

ClojureJavaみたいな型ベースのオブジェクト指向言語とは、かなり違う言語なのですが、一見「これってJavaのインターフェースと同じか」とか「これってLombokで@Valueでクラス定義するようなもの?」とか思えてしまう機能もあったりします。しかしもちろん、それらはイコールではないし、意図してることも異なってることもあります。
他の言語から移ってきた時には、「なんでそういう仕組みになっているのか?その背景はなんなのか?」を把握するのがとても大事だと思います。意図さえわかってしまえば、とても簡潔で書きやすい言語ですから。

抽象データ構造やAd-hoc多態以外にも、Clojureには、シンプルで構造化しやすい言語を作るために、いろんなアイデアが取り込まれていて面白いので、今後も、理解できた範囲で書いていきたいところです。

Clojureのいろんな並行処理の使い分け

この記事はもともとTumblrに書いていた自分のブログ記事を転載したものです。投稿日時も当時の投稿日時を再現してあります。

Clojureには標準でもagent系のsend, send-offに加え、future関数というスレッド起動系関数があります。
core.asyncの登場で、ここにgoマクロとthreadマクロが加わりました。

これらはすべて、背後ではJavaのExecutorsを使ってスレッドプールを作り、一度生成したスレッドの再利用を行いますが、それぞれ使っているスレッドプールが異なります。さらに関数自体の機能も異なるため、どれをつかったらいいのか迷ってしまうことがあります。

自分用に整理したので、メモとしておいておきます。

IOバウンドとCPUバウンド

まず、Clojureのスレッド関連関数の用途は、大きく2種類にわけられます。それが、IOバウンドとCPUバウンドです。

IOバウンドな処理は、実行中の処理がCPUよりもIO処理に強く依存します。DBアクセスとかリモート通信とかですね。別スレッドでこの処理を実行した場合、スレッドは大部分を、IO処理待ち状態で過ごします。
CPUバウンドな処理は、途中にIO待ちのような「待機」が発生せず、CPUをぶん回し続けるような処理です。全データがメモリに載っていて、CPUがフル稼働でそれらを処理するようなケースです。

IOバウンドな処理は大半をIO待ちで過ごすため、CPUを占有しません。一方CPUバウンドな処理は、その名の通り、動いている間中、CPUを使い続けます。

CPUを使い続けるような処理は、CPU(コア)数以上のスレッドを起動してもあまり意味がありません。たくさんのCPU依存処理を起動する場合、全スレッドがタスク処理でCPUを占有しているのがもっとも効率の良い状態で、それ以上起動しても、単にスレッド切り替えコストが無駄になるだけだからです。 CPUバウンドな処理は、スレッド数をコア数に近い数にとどめ、ひとつひとつのタスクは小さくして、たくさんのタスクをどんどんコアで分散して処理していくのが効率がよいことになります。CPUバウンドな処理はCPUを使うしかないのだから効率良く使いたいわけです。

一方、IOバウンドな処理は、その大半は「IO待ち」だったりします。リモートAPIを呼ぶ処理は、大半を「レスポンスが返ってくるのを待つ」ことに費やしています。

ここでたくさんのIOバウンドな処理を、コア数分の固定数スレッドで実行したことを想像してください。スレッドが4つだとして、4つのIO処理を起動すると…すべてのスレッドが使われ、それ以降の処理は待つしかありません。
CPUバウンドな処理であれば、スレッドはCPUをフルに使って一所懸命にタスクを実行していることでしょう。だから待つしかありません。しかしIOバウンドな処理では、4つのスレッドは、おそらく、ただIO待ちをしているだけです。

だから、IOバウンドな処理でスレッド数をコア数近くに限定するのは、あまり意味がないということになります。スレッドがIO待ちをしている間に、ほかの処理が動けるかもしれないのですから。だから、コア数以上のスレッドを起動して、どんどんIO待ちさせ、IOが終わったスレッドから処理を行えばよいのです。

固定数スレッドプールとキャッシュ化スレッドプール

Clojureの関数は、その用途がCPUバウンドかIOバウンドかによって、使用するスレッドプールが異なっています。

agent実行関数sendが使うスレッドプールは固定数であり、JavaのExecutors.newFixedThreadPoolメソッドで作られます。
一方、send-offが使うスレッドプールはキャッシュ化された非制限プールで、Executors.newCachedThreadPoolで作られます。非制限といっても、キャッシュ化スレッドプールは、使われなくなったスレッドを60秒で破棄するので、たくさんのスレッドがゴミとして残ることはありません。

多くのClojure関係の本で、sendはCPUに依存する処理に、send-offはIOに依存する処理に使う、と書かれているのは、このように、背後で使っているスレッドプールがことなるからです。

背後で使われているスレッドプールの種類がわかれば、その関数が、CPUバウンドな処理を想定しているのか、IOバウンドな処理を想定しているのかがわかります。以下は、Clojureのマルチスレッド関数がどのスレッドプールを使っているのかの一覧です。

 poolの定義場所プールの種類スレッドプール生成方法スレッド数
sendclojure.lang.Agent/pooledExecutor固定数Executors.newFixedThreadPool2+コア数
send-offclojure.lang.Agent/soloExecutorキャッシュ化Executors.newCachedThreadPool制限なし
future / future-call / pmap / pcallsclojure.lang.Agent/soloExecutorキャッシュExecutors.newCachedThreadPool制限なし
goclojure.core.async.impl.exec.threadpool/the-executor固定数Executors.newFixedThreadPoolコア数 * 2 + 42
thread / thread-callclojure.core.async/thread-macro-executorキャッシュExecutors/newCachedThreadPool制限なし
reducersclojure.core.reducers/poolForkJoinPoolnew java.util.concurrent.ForkJoinPool自動制御

futureのところにはpmapとpcallsも書いていますが、pcallsはpmapを、pmapはfutureを呼び出すので、すべてfutureと同じ扱いです。

まとめてみると、core.asyncの解説で必ず取り上げられるgoマクロは、固定数のスレッドプールを使っていることがわかります。つまり、goマクロはCPUバウンドな処理を前提としているわけです。

goマクロが「コア数 * 2 + 42」というよくわからないスレッド数を使っていることについて、特に42という謎の数値を指定していることについてははっきりしないのですが、+42は後から付け加えられたらしく、メーリングリストのポストなどを追跡すると、前述した、IOバウンドな処理に固定数スレッドプールを使った場合のような、IO待ちで全スレッドが停止して並行処理がスタックしてしまうことをある程度抑止したい、というのが意図のようです。goマクロはあくまでCPUバウンドな処理を扱うものであることは変わらないそうです。

42という数値については「すべての答え」から取ったのでは、という説もありますが、いまだ謎です。「すべての答え」ネタを知らない人はググってください。

goがCPUバウンドであるかわりにthreadマクロが用意されています。
threadマクロはgoマクロとほぼ同じ使い勝手で使えますが、キャッシュ化スレッドプールを使うため、IOバウンドな処理に向いています。goマクロと異なるところは一点だけで、チャネルの操作に <! と >! は使えず、ブロック型のチャネル操作関数 <!! と >!! を使う、ということです。<!!, >!! では呼び出した段階でスレッドがブロックしますが、そもそもthreadを使った場合はネイティブスレッドに処理が割り当てられていて、そのスレッドがブロックするだけなので、メインスレッドは止まらず、問題ありません。

goマクロで起動した並行処理は、単純にひとつのスレッドに丸ごと渡されるわけではなく、コンパイル段階で全処理が式単位に分解され、ステートマシンに変換されます。S式ならではです。そして<!, >!でチャネルへのアクセスごとにスレッドが切り替わる、といった動きをするようです。<!!, >!! をgoブロックで使うと、このスレッド切り替えがうまく動かなくなるので、&gt! か !< を使います。
彼らはこれをIoC Threadと読んでいますが、いやいやそれはIoCというよりも、昔の協調型マルチタスクと似たものだから「協調型スレッド」と呼ぶべきだという意見もあります。私も強調型だって意見に賛成ですが、たぶんIoCのほうがかっこいいってことなんだと思います)

reducersだけは特殊で、reducersは内部では並行処理をJava 7以降のFork/Join APIに処理を丸投げしています(JVMがJava7未満の場合は互換ライブラリを使っているようです)。Fork/JoinはJavaではとても使いにくいAPIで、Java 8でラムダ式とパラレルストリームが導入されてやっと本気出せるようになったのですが、ClojureではJava 8よりももっと前に、早々に対応していたわけです。よって性質としてはFork/Joinと同等でして、Fork/Joinのドキュメントによると、CPUバウンドな処理を前提にスレッド数を自動制御し、IOバウンドな処理が混ざるとうまく自動制御できないようです。

スレッドプールも、Java 7でFork/Joinとともに導入された、ForkJoinPoolを使っています。このプールは、初期値はCPUコアと同数のスレッドを用意し、ダイナミックにワーカースレッドを追加したり停止したりします。
つまり、reducersはFork/Joinにすべておまかせ、ということです。

そもそもFork/Joinは、要素数がとても多いデータ(10万とか100万とか)を高速並列処理するためのAPIなので、並列化が目的なら、数個程度の並列化ではreducersではなく別の機構をつかったほうがいいです。reducersの機能は並列化だけではないので、そっち目当てならよいですが。

プールの違い

表をよく見るとわかりますが、同じスレッド化プールを使っている関数でも、threadマクロだけは、プールが異なります。send-offとfutureは、ともにClojure標準関数なだけはあって、両方が同じスレッドプールを使っています。これはつまり、send-offで生成されたスレッドは、futureでも再利用できることを意味します。

core.async/thread は、そもそもcore.async自体がClojureの「外部ライブラリ」な位置づけですから、独自に定義したスレッドプールを使っています。よって、futureとthreadとは、互いに生成済みスレッドを再利用できません。ちょっとした差ではありますが、効率的ではないことは知っておいて損はないでしょう。

core.asyncを使う人は、おおむね、スレッド処理はcore.asyncばかり使う傾向があるので、今後はfutureの代わりにthreadを使うことにすれば落着、と行きそうですが、両者は機能にも違いがあるのでなかなかそうは行きません。

機能の違い

  • send, send-off (agent系)
  • future
  • go, thread (core.async系)

この3種類は用途および使い方が違います。
sendとsend-off、goとthreadは、用途は同じですがCPUバウンドかIOバウンドかが異なります。
futureはどちらにも属しません。

sendとsend-offはどちらも、agent操作関数であり、目的はあくまでagentの実行と更新です。そもそもagentは、汎用的な並行処理起動のためにあるものではなく、かなり特殊な用途でつかうものなので、「ただスレッドを起動したい」だけでは使わないほうがいいです。

agentの特徴は、同じagentで起動した処理は「逐次実行される」点です。同じagentに何回もsend, send-offしても、それらが平行で処理されるわけではありません。sendやsend-offはagentのアクション実行キューにアクションを積むだけです(もちろん、複数のagentが存在すれば、それらは平行に動きます)。そもそもagentは「値」を持っていて、sendやsend-offで積んだアクションによって、agentの結果値が順番に変わっていく、というものだからです。

goとthreadはagentに比べてより汎用的な並行処理機構で、goやthreadブロックの処理は、スレッドプールの違いはあれ、すぐにスレッドに割り当てられて平行に動きます。いずれのマクロも、処理完了時の結果値が取り出せるチャネルを返します。とこれだけ書くと、threadはfutureと似ているように思えます。ともにIOバウンドな処理用で、結果値を取得できるオブジェクトを返します。futureを卒業して、core.asyncに「移行」すべきでしょうか?

futureは、処理をキャッシュ化スレッドプールに渡してくれる点でthreadと同じですが、futureはdelayオブジェクトでもある点が大きく異なります。

(let [result1 (future (my-remote-func1 ...))
       result2 (future (my-remote-func2 ...))]
  (my-long-processing-fn)
  {:age (-> (:base @result1) (+ 20))
   :address (str (:address @result1) " " (:address @result2))
   :name (:name @result2)})

futureはderef(の省略記号アットマーク)によって非同期処理の実行結果を取得できますが、derefは何回でも使えます。最初のderef時にまだ処理が終わってない場合は処理完了を待機しますが、以降は、キャッシュした結果値を返し続けます。

上記例では、my-remote-func1とmy-remote-func2というリモート呼び出しを平行化するためにfutureを使い、さらにmy-long-processing-fnという長い処理を行う関数を呼びました。my-long-processing-fn実行中も、別スレッドでリモートコールは実行されています。
最後にマップを作るときに、futureの結果値を参照していますが、result1もresult2も、2回参照している点に注目してください。

threadマクロはdelayオブジェクトではなく、チャネルを返します。(<!! ch) によって結果値を取り出せますが、derefと違って、<!!を繰り返し読んでも同じ結果が返ってくるわけではありません。チャネルはキューの一種で、チャネルへの <!! は呼ぶたびに新しい値を返し、値がなくなるとnilを返すので、チャネルを、delayのように繰り返し参照すべきではありません。

(let [ch1 (thread (my-remote-func1 ...))
       ch2 (thread (my-remote-func2 ...))]
  (my-long-processing-fn)
  (let [result1 (<!! ch1)
        result2 (<!! ch2)]
      {:age (-> (:base result1) (+ 20))
       :address (str (:address result1) " " (:address result2))
       :name (:name result2)}))

チャネルベースのthreadを、futureの代用として使う場合は、letを使ってチャネルからいったん値を取り出さなければいけない点で、使い勝手が異なってきます。

もちろん、ごく僅かな差ですし、go/threadには、複数のgo/threadブロックが共通の(しかもたくさんの)チャネルを介して値をやり取りしつつ並行処理を実行するという本来の目的がありますから、価値はいささかも減じません。ここで言いたいのは、threadはgoのIOバウンド版であって、全並行処理をcore.async化しようとして、futureのかわりにthreadを使おうというのは、アリではありますが、若干短絡的です。
core.asyncのパワーは、goあるいはthreadブロックが複数個起動していて、互いに(チャネルを介して)通信しあう時に発揮されます。もちろん、常に結果チャネルを返す点で汎用的なスレッド起動の仕組みとして使うことも配慮されていますが、上記のような違いを意識しておいたほうがよいでしょう。この例のように、複数のfutureでいくつもの並列処理が起動して、あとでその結果値を使う場合、threadの場合は、長いlet式でいったんチャネルをリードする必要があるかもしれません。

一方で、futureとthreadは使用するスレッドプールが異なるので、併用すると、互いにスレッドを共有してくれません。future同士はスレッドを共有しますし、thread同士も共有しますが、futureとthreadは共有しません。ここに若干のロスが存在します。

よって、用途に合わせてfutureとthreadと使い分けるか、あるいはスレッドプールの効率性を考えて片方に寄せるか(パワーを考えるとthreadの方が強力なので、ふつうはthreadに寄せるでしょう)は、正直、好みの次第です。実を言うと、私はfutureを使うシーンでもthreadを使うことがほとんどです。好みの問題です。

まとめ

  • agentは単なる並列処理起動用の機能ではないので、ちょっと考えて使え
  • reducersはすごい量のデータを処理でもしない限り、並列化機構だと思うな。
  • いまやりたい処理がCPUバウンドかIOバウンドかはちゃんと考えろ
  • futureにはちゃんとfutureに向いた処理がある。けどあえてthreadで代用も出来る。その場合、他の並列処理もなるべくcore.asyncを使うようにすれば、スレッドプールのキャッシュ効率は若干良い。

Mac OS X 10.10 (Yosemite)にバージョンアップするとPostgreSQLが動かなくなった場合の対処方法

この記事はもともとTumblrに書いていた自分のブログ記事を転載したものです。投稿日時も当時の投稿日時を再現してあります。

Yosemiteにバージョンアップして大して問題もなく過ごしてたんですが、ひさびさにPostgreSQLを使おうとしたら、なぜか繋がらない。

could not open directory "pg_tblspc": No such file or directory.

とか言われてしまう。
いろいろ調べたところ、どうも、Yosemiteにアップデートする過程で、なんでかしらないが空のディレクトリが削除されるのではないか?というポストを見つけました。

http://stackoverflow.com/questions/25970132/pg-tblspc-missing-after-installation-of-os-x-yosemite

結局、次のようにディレクトリを作成すれば、問題なく起動しました。

mkdir /usr/local/var/postgres/pg_tblspc
mkdir /usr/local/var/postgres/pg_twophase
mkdir /usr/local/var/postgres/pg_stat_tmp

同じ問題にハマった人向けにメモしておきます。

Atlassian BambooでCI環境を構築する

この記事はもともとTumblrに書いていた自分のブログ記事を転載したものです。投稿日時も当時の投稿日時を再現してあります。

やらないといけないと思いながらも、ずぼらでずっと後回しにしてた、CIサーバ構築作業を最近やりました。というのも、JIRAを契約するついでにお試しに登録していた、Atlassian Bambooのお試し期間が過ぎてしまったから。お試しすらしていない…

とりあえず解約するにしても試してからにしようってことで本腰入れて使ってみることにしました。でも、ネットで検索してもCircleCIいいよってのが多く、Bambooは「どう設定していいのかぱっとみわからなかった」みたいなのが多い印象。

たしかに、ぱっと見どう使っていいのかなんか分かりにくい…

でも、分かってしまえば仕組みは簡単でした。

Bambooって結局なんなのか

Amazon EC2を実行サーバとして利用する、CI環境です。Bamboo上にソースリポジトリと「Builder」や「Tester」の設定をしておくと、ソースをリポジトリにpushすると、Bambooが指定したEC2インスタンスを自動起動して、ソースチェックアウトからビルド&テストしてくれます。EC2インスタンスは自動停止します(これについては後述)

ポイントは

  • AmazonのAMIを指定するとそのサーバを起動して使うので、ビルド&テスト環境を自由に構築できる(自分でEC2上に環境構築して、そのインスタンスからイメージをつくればいいので)。自由度は高い
  • Bambooのコストとは別にEC2のコストがかかる
  • ビルトインで提供されているBuilderとかを使える状況であれば、出来合いのAmazonインスタンスを使えば即使える

って感じですか。 私はClojure+Leiningen+Midjeという環境が必要なので、サーバ設定を自前でできるのはありがたい。使ってみようという気が湧きました。

ただ、ドキュメントが猛烈に分かりにくいように思います。何ヶ所かつまって、検索すると、Atlassian自身のフォーラムに同じ質問が(英語で)上がってて解決したり、みたいなことがけっこうありました。

ワナ

私が一日無駄にしたワナが、Bamboo OnDemand(ダウンロード版)では、Atlassian提供のAmazon EC2イメージを使ってサーバ構築しないと、Bambooが接続するためのエージェントが起動しないってのがよくわかってなくて、それが分からなかったのが

tokyoリージョンにはイメージが提供されてない

ってことがわからなかったからです。なんとかビルドできる環境作っても、エージェント起動開始でずっとPending…ってなって困ってたのですが、そもそもエージェント・プログラムがサーバに入ってないのだからあたりまえです。

でもねえ、「Elastic Bamboo Global Settings」ってところに、いきなりリージョン選べってのがあるんですよ。そりゃTokyo選びませんか?

Global Settings画面 global settings

Bamboo OnDemandを使う場合は、ここは(ちゃんと動く環境ができるまでは)US East (Northern Virginia)固定でOK。デフォルトがたぶんそうなってるので、かえなきゃいいわけです。

すべて動くようになってから、EC2のイメージを(EC2の機能で)Tokyoリージョンにコピーして、Bamboo側もTokyoリージョンに変更、ということは可能ですが、Bambooインスタンスに接続するのはBamboo自身であって、自分でも顧客でもないのだから、インスタンスがTokyoで起動する意味もないわけで、ずっとUS Eastでもいいはず。

US Eastであれば、Bambooの「Image Configurations」画面に、すぐに使用可能なイメージがずらっと表示されます。Tokyoに変えていると、ここが空っぽです(空っぽなので、ほんとはここに既成イメージが表示されるなんてことが想像できない)

image configurations

Elastic Bambooの設定画面でAWS access key idを設定済みであれば、Startボタンを押すと、EC2でインスタンスが起動します。あとはEC2インスタンスにSSHでログインして、ビルド環境を構築した後、EC2側でインスタンスからイメージを作成し、そのイメージを、Image Configurationsに追加してやれば、独自のビルドサーバが作成できます。

ただ、Java+Maven+JUnitとかRubyとかPHPとかは、実はビルドやテストを実行できるだけの設定がすでに既成イメージにされているので、上記から適当にひとつ選んでもいいかもしれません。ただLargeイメージを使う意味がない場合は、既成イメージのAMI idを使って、インスタンスタイプだけをMicroに変更したような定義を一個作った方が、財布に優しいと思います。

ともかく、US Eastリージョンで既成イメージをベースに環境構築する(あるいはそのまま使う)ってことだけ忘れなければ、そんなにはまることなくさっさと動かし始められると思います。ただEC2につないでるだけですから…

EC2インスタンスの自動停止

Bambooの設定に、リモートサーバのエージェントが何分以上アイドルになったらインスタンスを止めるかって設定があって、デフォルトが10分なんですが、10分経っても落ちません。実は、サーバの自動停止にはもうひとつ条件があって、なぜかドキュメントに書いてなくて、AtlassianフォーラムのQ&Aで見つけたのですが…

  • EC2の支払いが最小になるように、EC2の最小課金時間(通常は1時間?)の間は起動し続ける

ということのようです。

EC2のインスタンスは、起動サーバ単位で1時間いくら、なんですが、この1時間というのは「1時間未満はすべて1時間とみなす」という条件がついてます。よって、1分で停止しても1時間分取られます。

Bambooは、ソースがプッシュされるたびに毎度サーバを起動&停止するのではなく、1時間以内であれば、同じインスタンスを使い回そうとします。アイドル時間が10分たっても、1時間以内にまた別のビルド要求が来るかもしれないので、ギリギリまでは起動し続けます。そのまま要求がこないと、最小課金時間が過ぎるちょっと前に(私が見てた限りでは55分くらいで)自動停止します。 なかなかがんばってるなーと思いました。

Capabilityの設定

Bambooに設定できる各種コマンドは「Capability」と呼んで、インスタンス設定画面の既成インスタンス一覧にある「View Capabilities」を押すと、そのイメージにあるコマンド等を設定できます。

Ant, Grails, Maven, Node.js, PHPUnit, JDK, Mercurial, Gitは始めから入っています。あと、設定画面には出ていないけど、サーバにログインしてみると、/optの下に既にJDK8やMaven 3.2が入ってたりもするので、それらは、サーバをいじらずに、Capabilityに定義を追加するだけで、使い始められます。

私はClojureソースをビルドするためにLeiningenをサーバに入れたので、次のように定義を足しました。

leiningen

このようにCapabilityを足すと、この後のタスク設定なんかで、「Leiningen」を選択できるわけです。

ステージとジョブ、タスク

ビルドプランの設定はまあ直感的だと思います。 ステージ・ジョブ・タスクの区別が分かりにくいかなーと思いました。

  • ひとつのプランは複数のステージを持てる
  • ステージは順番に実行される
  • ひとつのステージは複数のジョブを持てる
  • ジョブは 並列に 実行される
  • ジョブは複数のタスクを持てる。
  • タスクは順番に実行される

という構造で、ジョブが並列に実行されるってことだけ分かってればいいかと。

以下の画像では、

  • 「Build Project」という名前のステージが
  • 「Build Source」というひとつのジョブを持っていて、そのジョブが
  • 「Source Code Checkout」「Maven 3.x」「Command」という3つのタスクを持っている

という構造です。

plan

タスクは、Bambooにもともといくつか用意されていて(「Source Code Checkout」とか)、それらを並べるだけでいいです。サーバイメージに自分でインストールしたコマンドについては、「Command」タスクを使って、Executableを指定できます。

こんな感じです。先にCapabilityを設定しているので、コンボボックスでLeiningenを選択できるようになっています。

task settings

上記イメージのように、JVM系コマンドの場合、JAVA_HOMEとPATHをせっていしなくちゃいけません。Bambooエージェント自体はJava 6で動いている(サーバ設定いじると変えられますが、エージェントが起動しなくなります)ので、タスクの設定で、使用したいJVMを指定してください。 上記画像ではJava 8を設定しています。ちなみに、Java 8は既成イメージの始めから入ってました(なぜかCapabilityには入ってないので、自分で足しましたが)

ビルドしたいソースが、チェックアウトしたソースのサブディレクトリの場合は、「Working sub directory」にリポジトリトップからの相対パスを書きます。私の場合、javaソースは「java」というサブディレクトリに、Clojureソースは「clojure」というサブディレクトリに入れているので、「clojure」を指定しています。

言葉で書くと長ったらしいですが、設定自体はさくさく終わります。

あとはビルドプランを一発手動実行して、動くようならOK。リポジトリにソースがプッシュされたら、勝手にビルドが走ります。

Bambooについて日本語で書いてるブログがほとんど見つからなかったので、書いてみました。

シェルスクリプトでtmuxのウインドウをまとめて開く

この記事はもともとTumblrに書いていた自分のブログ記事を転載したものです。投稿日時も当時の投稿日時を再現してあります。

開発中、常に5つくらいのウインドウをtmuxで開いて作業しています。開くウインドウは毎回同じなのですが、毎回毎回5つウインドウを作って、リネームして、というのが面倒です。

たぶんコマンドから制御できるんだろうと思い調べたらやっぱりあったので、メモとして書いておきます。

DIR="/path/to/your/development/dir"
tmux new-session -d  -s myproj -n project1 -c "$DIR/project1"
tmux new-window -n project2 -c "$DIR/project2"
tmux new-window -n project3 -c "$DIR/project3"
tmux new-window -n project4 -c "$DIR/project4"
tmux new-window -n project5 -c "$DIR/project5"
tmux attach -t myproj

これで、tmux.shを起動したら5つのウインドウがセッティングされた状態でtmuxが開きます。

スレッドマクロを整理する

この記事はもともとTumblrに書いていた自分のブログ記事を転載したものです。投稿日時も当時の投稿日時を再現してあります。

スレッドマクロって?

スレッドマクロ(threading macro)は、Clojureのソースを人間に読みやすい形で書けるマクロで、現在のClojure 1.5.1には、結構な数のが用意されています。1.5で初めて追加されたものもありますし、まとめておくと、今後Clojureを始める人にも役立つかもしれないなあってことで、ブログ記事に書いておくことにしました。1.5で導入された新しいスレッドマクロも含めて既に知っている人には役に立たないのであしからず。

「スレッド」マクロといっても、並列プログラミングのスレッドとはまったく関係がないです。Clojureの -> や ->> のような、矢印系マクロの総称として使われています。Clojure 1.5.1では、次のスレッドマクロがあります。

  • ->
  • ->>
  • as->
  • some->
  • some->>
  • cond->
  • cond->>

基本は -> と ->> で、他のものは、この基本スレッドマクロの便利版と思ってよいです。

スレッドマクロの役割は、関数ベースのClojureでは「実行する順」に書けない、もしくは書いても冗長になるコードを、簡便に、「実行する順」に書けるようにすることです。「AしてBしてCする」みたいな処理を簡単に書けるようにするわけです。

(func3 (func2 (func1 :arg) :arg) :arg)

これを、

(-> (func1 :arg)
    (func2 :arg)
    (func3 :arg))

と書けるようにします。

-> (thread-first) マクロ

-> はその性質から「thread-first macro」と呼ばれるようです。->マクロは、初期値と、スレッディング対象となる式を受け取ります。実物を見た方がわかりやすいです。

(-> {:type :person
     :age  30}
  (assoc :name "John Doe")
  (assoc :city "tokyo")
  (dissoc :age))

この例では、初期値は {:type :person, :age 30} というマップです。この初期値が、次のフォームである (assoc :name "John Doe") の第1引数の位置に挿入されます。
続けてこの (assoc 初期値 :name "John Doe") が、2つ目のフォームである (assoc :city "tokyo") の第1引数になります。さらにこの assoc が、最後のフォームである (dissoc :age) の第1引数になるのです。

assoc を2回呼ぶなら可変長引数でひとつにまとめろ、という話はちょっと脇へ置いておいてください)

式が挿入される位置を x で表現してみると、次のように、

(assoc   x :name "John Doe")
(assoc   x :city "tokyo")
(dissoc  x :age)

式が挿入される位置に書いた x が、各フォームを貫いているように見えます。
このように、前の引数を次のフォームの同じ位置で使う場合に、読みやすいコードを書けるわけです。

もしスレッドマクロがなければ、前述のコードは、次のような関数の入れ子として書かなければいけません。

(dissoc (assoc (assoc {:type :person, :age 30} :name "John Doe") :city "tokyo") :age)

とても読みにくいです。無理矢理でも実行順に書こうとすると、let を使って各結果値をシンボルに束縛しなければならないでしょう。

(let [init {:type :person, :age 30}
      x (assoc init :name "John Doe")
      y (assoc x :city "tokyo")]
  (dissoc y :age))

let の各シンボルは使い捨てみたいで、なんだか疲れます。スレッドマクロであれば、直前の結果を次の式ですぐに使う、というコードを、簡潔に記述できます。

-> はスレッドマクロの基本であり、ほかのものは、「-> とどこが違うのか」で説明できます。

->> (thread-last) マクロ

->>マクロと->マクロの違いは、引数が入る位置が、第1引数ではなく、最終引数の位置になる、という点だけです。Clojureの関数の多くは、関数の操作対象を第1引数で受け取るように作られているのですが、第1引数として関数を取る関数の多くは、操作対象を最終引数で受け取ります。map, filter, distinctといった、コレクション操作系の関数のほとんどが、そのような作りになっています。

->>マクロを使えば、コレクションを filter して map して distinct する、といったコードを綺麗に記述できます。

(->> (my-great-func-returns-coll)
     (filter #(not (nil? %))) ;nilをはぶく
     (map :name)              ;:nameだけのリストにする
     (distinct))              ;重複をのぞいたリストを作る

->>マクロもスレッドマクロの基礎の一つで、このマクロがあるので、他のスレッドマクロの矢印の>が2つある場合は、フォームの最後に直前の式が挿入されるのだな、という暗黙の了解ができています。

as->マクロ

as->マクロはちょっと特殊で、->マクロの不便なところを補う役割があります。

->では、式が挿入される位置が「第1引数の位置」に固定されています。しかし、時には、式を別の位置に挿入したいこともあるはずです。そういう場合は、次のような、ちょっとアクロバティックなコードを書く必要がありました。

(-> {}
  (assoc :type ::book)
  (#(my-great-func arg1 % arg3))
  (dissoc :result))

引数をひとつ受け取る匿名関数を生成して、それを呼び出すためにさらにカッコで囲うわけです。すると、その匿名関数の第1引数となる位置に、直前の式が挿入されるのです。

また、スレッドマクロの途中には関数しかおけないので、途中にログを出力する処理を挟みたいと思っても、ログを出力して引数をそのまま返すような関数を定義しなければいけません。簡単にかきたいからスレッドマクロを使っているわけで、ちょっと本末転倒なところがあります。

つまり、処理中の値(直前の式の結果値)を補足できればいいわけです。それを行うのが、as-> マクロの役割です。このような用途であるため、as->マクロは、スレッドマクロの内側で使うことを前提にしています。もちろん、自分で第1引数に値を渡せばスレッドマクロ外でも動きますが、そうしても特に役立ちませんし。
さらに、as->マクロは->系統のスレッドマクロ (->, some->, cond->) でしか使えません(->>系統のマクロでは使えません。as->>というマクロはありません)。

(-> {:result "test"}
  (assoc :type ::book)
  (as-> x (my-great-func1 :a x :b)
          (my-great-func2 x))
  (dissoc :result))

as-> x によって、直前の結果値を x に束縛できます。あとは、シンボル x を使って、my-great-funcの好きな位置に、直前の結果値を挿入できます 。さらに、my-great-func1の実行結果は再び x に束縛されます。my-great-func2に渡される x は、(assoc :type ::book)の結果ではなく、my-great-func1 の結果になります。

as->を使うと、スレッディングの途中の値を、ログ出力することも出来ます。

(-> {}
    (assoc :test "1")
    (as-> x (do (println (str "x = " x)) x))
    (assoc :test2 "2"))

この例では、(assoc :test "1")の結果を x に束縛し、ログ出力したあと、そのまま x を返しています。(assoc :test "1") の結果はそのまま (assoc :test2 "2") に渡ります。スレッドマクロの途中に、手軽にログ出力を挿入したわけです。

as-> が導入されたおかげで、アクロバティックなコードを書く必要がなくなりました。

some->マクロ、some->>マクロ

some-> マクロは、-> マクロのシンプルな拡張です。同様に、some->> マクロは ->> マクロの拡張です。
スレッディング処理を実行中に、途中で結果が nil になることがあります。nilになったらもう後続の処理は実行する意味がないとか(Clojureの多くの関数は nil を渡すと単に nil を返しますが、確実ではないし、無駄でもあります)、後続にJavaのメソッドをコールする部分があって、nil を渡すと NullPointerException になってしまう、ということはあり得ます。

some-> は、スレッディング途中に結果が nil になったら、そこで処理を打ち切って nil を返却します。

(defn die [v]
  (when (nil? v) (throw (NullPointerException.))))

(some-> [:a :b :c]
  next
  next
  next
  die)

ベクタ[:a :b :c]に対して、「先頭要素を省いたシーケンスまたはnilを返す」という動作をするnext関数を3回使うと、結果はnilになります。->マクロであれば、最後にnildie関数に渡され、NullPointerExceptionがスローされます。
some->であれば、3回目のnextで結果がnilになった段階でnilを返すので、dieは実行されず、例外はスローされません。

実開発では、意外に使い勝手のいいスレッドマクロです。

cond->マクロ、cond->>マクロ

cond->cond->> の違いも、some->, some->> と同じく、引数の挿入位置だけです。それぞれ->あるいは->>と同じ場所に式を挿入します。

cond->は、条件付きスレッドマクロです。次のように、条件式と、実行式のペアを記述していきます。

(let [v {:age 30, :place :japan}]
  (cond-> v
    (>= (:age v) 20)      (assoc :adult true)
    (= :us (:place v))    (update-in [:recommended] conj "Bank of America")
    (= :japan (:place v)) (update-in [:recommended] conj "MUFG")))

;;結果→ {:age 30, :place :japan, :adult true, :recommended ["MUFG"]}

cond->は、初期値以降に、まず条件式を、次にその条件が真だった場合に実行する式を書きます。かならず2つをペアで書かなければなりません。
直前の式は、右側の式の第1引数の位置に挿入されます。左側(条件式)には挿入されないので注意が必要です。

cond式と異なり、cond->では、全ての条件式が評価され、真であれば、右の式を評価します。このとき、右の式に対してスレッディング処理が行われます(第1引数もしくは最終引数の位置に、直前の式が挿入される)。条件式が真でない場合は、対となる右側の式はスキップされ、次の条件式が評価されます。

前述の例なら、(= :us (:place v)は真ではないので、(update-in [:recommended] conj "Bank of America")は実行されず、最終結果の:recommendedにも"Bank of America"は入っていません。

スレッドマクロを使いたいのだけど、スレッディング内の式の一部だけを条件付きで実行したいときに便利です。意外と、そういうことは多くあります。特にcond->>マクロであれば、コレクション操作を条件付きで実行できるので便利です(特定のパラメータがオンのときだけ、最後にソートを実行する、など)。

まとめ

スレッドマクロは、関数スタイルで書くととても読みにくくなるコードを、劇的に読みやすくしてくれる便利なマクロで、人気も高いようです。標準のマクロ以外にも、オープンソースで公開されている独自マクロもあったりします。やはり「AしてBしてC」という処理は、同じ順序に書いたほうが分かりやすいですからね。中には、かなり実用的なものもあります。ここにある、Diamond Wandなんかはかなり使えます。

ただ、便利だからといってたくさん導入すると、コードが矢印だらけになって、読むのが大変になるかもしれません。Clojureに標準で存在するスレッドマクロを基本にして、ほかのものはチームと相談しつつ導入、というほうがよいかもしれないですね。

->->>はClojureの初期から存在したので知っていても、cond->some->, as->は知らない、ということもあるかと思います。とても便利なので知っておいて損はないでしょう。