内容紹介
Clojure(クロージャ)は、関数型プログラミングや並行プログラミングといった、先進的で強力なプログラミング手法をサポートするプログラミング言語。進んだパラダイムと実用性との見事なバランス、オープンソースでの公開やJava との親和性の高さなどから、注目を集めつつある。
本書は、ユーザにとって必要な知識を十分に解説したClojure の実践的な解説書Pragmatic Bookshelf 社の“Programming Clojure”を翻訳したもの。基本部分から応用的なところまでを網羅的に解説している。
出版社からのコメント
Clojure(クロージャ)は、関数型プログラミングや並行プログラミングといった、先進的で強力なプログラミング手法をサポートするプログラミング言語。進んだパラダイムと実用性との見事なバランス、オープンソースでの公開やJavaとの親和性の高さなどから、注目を集めつつある。
本書は、ユーザにとって必要な知識を十分に解説したClojureの実践的な解説書Pragmatic Bookshelf社の"Programming Clojure"を翻訳したもの。基本部分から応用的なところまでを網羅的に解説している。
★このような方におすすめ
プログラマ全般
特に、Haskell、Erlang、 Lispなどに興味がある先進的なプログラマ
著者からのコメント
元来、Lisp 族言語はプログラミング言語のアイディアの実験場だった。Lispの本質とも言える拡張性の高さ、およびコアとなる仕様のシンプルさがアイディアを実装し、使い、改善してゆくプロセスを容易にするからだ。しかし言語の自由さと安定した仕様との間には相反する関係がある。ひとたび言語が広く使われだすと、言語の根幹にかかわる仕様、例えば評価規則を変えるなどの変更は難しい。したがって、伝統から離れる新しいアイディアを実現した言語も、ユーザが増えるにつれ漸進的な進化に落ち着いてゆく。
そうやって枯れることは諸刃の剣だ。漸進的進化に安住してしまうと発想の源が制限されてしまう。時には連続性を捨てた異端児が生まれることが、言語の一族が進化してゆくためには必要なのだ。Clojure は、Lisp 一族にとって非連続な異端児と言える。Clojure の新しいアイディアが実戦を経て生き残るかどうかを見極めるにはまだ時間が必要だが、特に並行モデルに関しては他の言語を大いに刺激するものだろう。
もっとも、Lisp に馴染みのない読者にとってはClojure も他のLisp も似たようなものに見えるだろう。本書はLisp の知識を持たない人でも自然に読み進められるようになっているので、Lisp がどうの、という話は気にせずに(そして見慣れぬ括弧にあまり惑わされないようにして)、ひとまずは本書の例を実際に試してみてほしい。特に、小さいが強力な部品を組み合わせることで、高機能を簡潔に実現できるところに注目してみよう。そこには、言語を超えた普遍的なアイディアが示されている。本書で得た知識はClojure を使うのに役立つだけでなく、他の多くのメインストリーム言語に取り入れられつつある関数型プログラミングの概念を掴むのに効果を発揮するだろう。
......
(「訳者あとがき」より)
カバーの折り返し
* Clojureはエレガントだ。
* Clojureは「Lisp・リローデッド」だ。
* Clojureは関数型言語だ。
* Clojureは並行プログラミングを簡単にする。
* ClojureはJavaを歓迎する。
* 他の多くの人気のある動的言語と違って、Clojureは速い。
Clojureは今起こりつつあるより大きな現象の一部だ。Erlang、F#、Haskell、Scalaといった言語が、関数型プログラミングや並行プログラミングをサポートすることで注目を集めてきた。これらの言語の熱心なユーザなら、Clojureにも共通する土壌を見て取ることができるだろう。(著者まえがきより)
...Stuartのこの本を読んで、ぞくぞくした。彼がClojureの意味をいかに深く理解してくれたかが分かったからだ。Clojureは、彼のようなプロの開発者に向けた言語だ。(Clojureの作者、Rich Hickeyが寄せた序文より)
著者について
Stuart Halloway はRelevance, Inc. の共同創設者であり、CEO を務める。Relevance が提供するサービスは、アジャイル手法および、Ruby やClojure といった先端技術にかかわる開発、コンサルティング、トレーニングである。本書以外の著書にComponent Development for the Java Platform(Addison-Wesley, 2001)やRails for Java Developers(Pragmatic Bookshelf, 2007)などがある。
訳者 川合史朗(かわいしろう)
Scheme Arts, LLC 代表。Scheme 処理系Gauche をツールとしてソフトウェアコンサルティングを行う。強みはネットワーク、CG コンテンツ制作プロセス、言語処理系。2002 年まではSquare USA Inc. にて、Senior Software EngineerとしてCG 技術開発に従事。たまに地元ホノルルの劇場で舞台に立つ役者にもなる。訳書に『ハッカーと画家』(オーム社、2005 年)、共著に『プログラミングGauche』(オライリージャパン、2008 年)がある。
著者略歴 (「BOOK著者紹介情報」より)
Relevance,Inc.の共同創設者であり、CEOを務める
川合 史朗
Scheme Arts,LLC代表。Scheme処理系Gaucheをツールとしてソフトウェアコンサルティングを行う。2002年まではSquare USA Inc.にて、Senior Software EngineerとしてCG技術開発に従事。たまに地元ホノルルの劇場で舞台に立つ役者にもなる(本データはこの書籍が刊行された当時に掲載されていたものです)
About this Title
Getting Started
まず、Clojure を魅力的なものにしている特徴的な機能をざっと見ることから始めよう。
エレガントさ、高い表現力
コードとデータが同じ表現であるというLisp の力
容易で高速なJava とのやりとり
すべてのデータを統合するシーケンスライブラリ
再利用が容易で正しいコードを書きやすい関数型プログラミング
ロック管理の悪夢から開放された並行性
この機能のリストは本書全体のロードマップにもなっているので、今ここで細かいことが分からなくても心配しないでいい。それぞれの機能が、後
で1 章まるまる割いて説明される。
次に、早速だが小さなアプリケーションを組み立ててみることにしよう。また、本書の後のほうで使うことになる、大きな例をロードして実行する
方法についても説明する。
最後に、依存関係を基にしたビルドシステムであるLancet を紹介する。Lancet は本書を通じて作り上げてゆくことになるサンプルアプリケーショ
ンだ。
1.1 なぜClojure なのか
ぱっと見ると、Clojure は近未来からやってきた汎用言語のようだ。Clojureの関数型プログラミングとソフトウェアトランザクショナルメモリは
、現在広く使われているプログラミング手法からずっと進んでおり、マルチコアハードウェアに非常に適している。
一方でClojure は過去と現在にもしっかりと足をつけている。Clojure はLispとJava 仮想マシンとを結びつけたのだ。Lisp はプログラミングの歴
史の黎明期から現在に至る叡智をもたらし、Java は頑健性、膨大なライブラリ、そして現代のあらゆる場所で使われているプラットフォームをも
たらした。
この2 つの組み合わせがどんなに強力か、これから見ていくことにしよう。
Clojure はエレガントだ
Clojure のコードはS/N 比が高く、結果としてプログラムは短くなる。短いプログラムは開発のコストを抑えられ、楽に運用を始められ、メンテナ
ンスも容易だ。*1 プログラムが単に短いというのではなく、小さくまとまっているということが特に重要である。例えばApache Commons にある次
のJava コードを見てみよう。
Icode/snippets/isBlank.java
public class StringUtils {
public static boolean isBlank(String str) {
int strLen;
if (str == null || (strLen = str.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if ((Character.isWhitespace(str.charAt(i)) == false)) {
return false;
}
}
return true;
}
}
isBlank メソッドは文字列がブランクかどうか、つまり空文字列あるいは空白文字だけからなる文字列かを調べる。Clojure で同じ機能を書くと次
のコードになる。
Icode/examples/introduction.clj
(defn blank? [s] (every? #(Character/isWhitespace %) s))
Clojure のほうが短いが、より重要なのは、Clojure のほうが簡潔だということだ。変数も、書き換えられる状態も、分岐もない。これは高階関数
のおかげだ。高階関数とは、関数を引数にとったり戻り値として返すような関数のことだ。every?は関数とコレクションを引数に取り、コレクショ
ン中のすべての要素について関数が真を返したときにtrue を返す。
Clojure 版は分岐が無いので、読むのもテストするのも容易だ。こういった利点はプログラムが大きくなるにつれ威力を発揮するようになる。また
、コードは簡潔であるけれど読みやすい。実際、上のClojure プログラムはブランク文字列の定義のように読める。「文字列は、その中のすべての
文字が空白であればブランク文字列である」(a string is blank if every character in it is whitespace)、と。これはCommons のメソッドよ
りもずっといい。Commons のメソッドのほうはブランク文字列の定義がループやif 文といった実装上の詳細の中に埋もれてしまっている。
次の例として、Java で単純なPerson クラスを定義してみよう。
Icode/snippets/Person.java
public class Person {
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
一方、Clojure でのperson の定義は1 行だ。
(defstruct person :first-name :last-name)
defstruct と関連する関数については2.1「マップ、キーワード、構造体」(p. 32)で詳しく扱う。
桁違いに短いというだけでなく、Clojure のアプローチはperson が変更不可能であるという点でも異なる。変更不可能な構造は自然にスレッドセ
ーフになる。構造体のアップデートは、第6 章「並行プログラム」(p. 157)で扱う、参照、エージェント、アトムといった機能を組み合わせるこ
とで実現できる。構造体が変更不可能であることから、Clojure ではhashCode() とequals() の正しい実装が自動的に実現できる。
Clojure にはたくさんのエレガンスが備わっているが、それでも足りないものがあれば、あなたは自分でそれを追加できる。それがLisp の力だ。
Clojure は「Lisp・リローデッド」だ
Clojure はLisp である。何十年にもわたって、Lisp の熱心な支持者は、あらゆる言語に対してLisp の利点を主張してきた。けれどもLisp による
世界征服計画はなかなか進んでいないようだ。
他のLisp と同様に、Clojure も2 つの壁を乗り越えなければならない。
Lisp プログラマに対しては、Clojure がLisp のエッセンスを継承しているということを納得させる。
同時に、過去のLisp が成功しなかった、より広いプログラミングコミュニティからの支持を得る。
Clojure はLisp のメタプログラミングの力を提供すると同時に、いくらかの構文的な拡張を導入して非Lisp プログラマにも親しみやすいようにす
ることで、これらの壁を越えようとしている。
どうしてLisp なんだろう?
Lisp は構文をほとんど持たない代わりに、とても小さな核となる言語と強力なマクロ機能を持つ。それらを使って、プログラマはLisp を目的とす
る設計に合わせて変化させることができる。設計のほうを言語に合わせて修正するのではなく。一方、Java ではどうだろう。次の例をみてほしい
。
public class Person {
private String firstName;
public String getFirstName() {
// 続く...
getFirstName() はメソッドだ。メソッドは多態的で、プログラマが好きなように定義を変えられる。けれども、この例に出てくる他の単語のほと
んどは、言語によって意味が決められてしまっている。ときに、これらの単語の意味を変えたいと切に思うことがあるはずだ。例えば次のような例
が考えられる。
private を「製品コード中ではprivate だけれど、シリアライゼーションとユニットテストからはアクセス可能である」というふうに再定義する
。
特に指定しない限りprivate なフィールドのgetter とsetter を自動生成するようにclass を再定義する。
あるいは、class に対してそのライフサイクルイベントへのコールバックを提供するサブクラスを作成する。ライフサイクルイベントとは、例え
ばそのクラスのインスタンスが作られるたびにイベントコールバックを呼び出すといったものだ。
こういった拡張すべてを必要とするプログラムを私はいくつも見てきた。これらの拡張無しでは、プログラマは似たようなコードを何度も何度も書
くことになり、ミスも混入しやすい。文字どおり何百万行にも及ぶコードが、プログラミング言語の欠けている機能を補うために書かれてきた。
ほとんどの言語では、上に挙げたような機能を言語に組み込んでもらうには言語実装者にお願いして待つしかない。Clojure なら、あなたは自分の
言語機能をマクロを使って追加することができる(第7 章「マクロ」(p. 193))。Clojure 自身さえ、defstruct のようなマクロを使って書かれ
ているのだ。
(defstruct person :first-name :last-name)
違うセマンティクスが必要なら、自分のマクロを書けばいい。もしフィールドが静的に型付けされて、しかもnull チェックの有無が指定できるよ
うなstructの拡張が欲しければ、そういったdefrecord マクロを書くことができる。例えばそれはこんな風に使えるだろう。
(defrecord
person [String :first-name String :last-name]
:allow-nulls false)
言語の中からその言語を変えてしまうというのはLisp に特有の利点だ。この利点は以下に挙げるようなさまざまな側面を持つ。
Lisp は同図像性(homoiconic)*2を持つ言語だ。すなわち、Lisp のコードはLisp のデータでもある。これによって、「プログラムを生成する
プログラム」というのが簡単に書けるようになる。
また、言語のすべてが、常にそこにある。Paul Graham はエッセイ「技術野郎の復讐」*3*4で、これがどうしてそんなに強力なのかを説明してい
る。
Lisp の構文はまた、演算子の優先順位や結合性といった規則を不要にしている。演算子の優先順位だとかどの演算子が右結合でどれが左結合かと
いった表は本書のどこにもない。すべて括弧で表現するので、曖昧性が無いからだ。
Lisp のこの単純で規則的な構文はしかし、少なくとも初心者にとっては欠点にもなりうる。Lisp のコードは括弧だらけになり、またデータ型とし
てリストばかりが強調されてしまうからだ。Clojure はLisp 以外のプログラマがLisp に近づきやすくなるようにいくつかの工夫をしている。
括弧の少ないLisp
Clojure は他のLisp から来たプログラマに対して強力な利点を提供する。
Clojure はLisp の具体的なリスト構造をシーケンスと呼ばれる抽象構造として一般化した。シーケンスは、リストの力を保ったまま、その力を
他のさまざまなデータ構造へと適用可能にしたものだ。
Clojure はまた、JVM を前提とすることで、強力な標準ライブラリを手に入れ、多くのプラットフォームに展開することが可能になった。
Clojure のシンボル解決方法とクオート構文は、よくあるマクロの多くを簡単に書けるようにする。
もっとも、Clojure のプログラマの多くはLisp に触れるのが初めてで、おそらくLisp と聞くと悪名高い括弧のことを思い出すだろう。Clojure は
確かに括弧を使うし、それによるLisp の力を保ってはいるが、いくつかの点で従来のLispの構文を改善している。
まず、Clojure はリスト以外にも正規表現、マップ、セット、ベクタ、およびメタデータといったさまざまなデータ構造に対してリテラル構文を
提供している。これによってClojure のコードは他のLisp ほどに「リストだらけ」には見えない。例えば関数の引数はリスト() ではなくベクタ[]
を使って指定される。
Icode/examples/introduction.clj
(defn hello-world [username]
(println (format "Hello, %s" username)))
ベクタを使うことで、引数リストがぱっと見て分かりやすくなり、Clojureの関数定義は読みやすくなる。
また、他の多くのLisp と違い、Clojure ではコンマは空白文字だ。コンマを使うことでデータ構造を読みやすく書くことができる。例えばベク
タは、コンマを使うと他の言語の配列のように見える。
; ベクタを他の言語の配列のように表記する
[1, 2, 3, 4]
-> [1 2 3 4]
典型的なClojure コードは、必要以上に括弧をネストすることはしない。例えば、Common Lisp にもClojure にもあるcond マクロを見てみよ
う。cond はテスト式と結果式からなるいくつかのペアに展開され、最初にtrue を返すテスト式に対応する結果式が評価され返される。Common
Lisp ではテスト式と結果式の各ペアが括弧でくくられる。
; Common Lisp のcond
(cond ((< x 10) "less")
((> x 10) "more"))
Clojure は余分な括弧を使わない。
; Clojure のcond
(cond (< x 10) "less"
(> x 10) "more")
これは好みの問題で、どちらの方式にもそれぞれ支持者がいる。重要なのは、Clojure では、Lisp の力を失わない限りにおいて、できるだけLisp
っぽくないようにできる機会があればそちらを選択しているということだ。Clojure は、Lisp のエキスパートにとっても初心者にとっても、優れ
たLispである。
Clojure は関数型言語である
Clojure は、Haskell のように純粋ではないが、関数型言語だ。関数型言語は次に挙げる特徴を持っている。
関数は第一級のオブジェクトである。つまり実行時に関数を作ったり、引数に渡していったり、戻り値にしたりと、他のデータ型と同じように使
える。
データは変更不可能である。
関数は純粋、つまり副作用を持たない。
多くの目的において、関数型プログラムは理解しやすく、エラーが入り込みにくく、そしてずっと再利用しやすい。例えば、次の短いプログラムは
データベースを検索して``Requiem'' と名の付く曲を作った作曲家を列挙するものだ。
(for [c compositions :when (= "Requiem" (:name c))] (:composer c))
-> ("W. A. Mozart" "Giuseppe Verdi")
for はループではなく、リスト内包表記を表現する。上のコードは次のように読める。「compositions 内のそれぞれのc について、もしc のname
が"Requiem"であれば、c のcomposer を結果とせよ。」リスト内包表記については4.2「シーケンスの変換」(p. 102)で詳しく解説する。この例
は、望ましい特徴を4 つ示している。
まずこのコードは簡単だ。ループも、変数も、変更される状態もない。
このコードはスレッドセーフだ。ロックは必要ない。
このコードは並列化可能だ。コードを変えることなく、各ステップのそれぞれを複数のスレッドで実行できる。
最後に、このコードはジェネリックだ。compositions は単なるセットであっても、XML であっても、あるいはデータベースの問い合わせ結果で
あってもいい。
関数型プログラムを、文によって明示的にプログラムの状態を書き換えていく命令型プログラムと比べてみよう。多くのオブジェクト指向プログラ
ムは命令型で書かれ、上に挙げた特長をひとつも持っていない。必要以上に複雑で、スレッドセーフではなく、並列化も一般化も難しい(関数型と
命令型のより詳しい比較を知りたければ2.6「for ループはどこにある?」(p. 51)を参照してほしい)。関数型言語の利点が知られるようになっ
てからしばらく経つが、Haskell のような純粋関数型言語はまだ世界を征服していないようだ。開発者にとって、普段の仕事を純粋関数型言語の枠
組みに当てはめるのは簡単なことではないからだ。Clojure が、これまでの関数型言語よりも開発者の興味を集めると考えるには4 つの理由がある
。
関数型言語の必要性はかつてないほど高まっている。多数のコアを持つハードウェアはもうすぐそこまで来ていて、関数型言語はその多数のコア
を活用するための明確な方法を提供する。関数型プログラミングについて詳しくは第5 章「関数型プログラミング」(p. 127)で触れる。
純粋関数型言語は、本当に変更が必要な状態をモデル化するのにはちょっとまどろっこしいところがある。Clojure は、ソフトウェアトランザク
ショナルメモリや参照(ref)、エージェント、アトム、そして動的束縛を使うことで、変更可能な状態を管理する構造的な方法を提供している(
ソフトウェアトランザクショナルメモリと参照については6.2「ref とソフトウェアトランザクショナルメモリ」(p. 159)、エージェントについ
ては6.4「非同期な更新にエージェントを使う」(p. 168)、アトムについては6.3「アトムを使った非協調的、同期的な更新」(p. 166)、動的束
縛については6.5「var でスレッドごとの状態を管理する」(p. 172)で説明する)。
多くの関数型言語は静的型を採用している。Clojure の動的型は、関数型言語を学び始めたばかりのプログラマにとっては近づきやすいだろう。
Clojure からJava を呼び出す方法は関数型ではない。Java を呼び出すと、慣れ親しんだ変更可能な世界に入ることになる。これは、関数型言語
にまだ馴染んでいない初心者にとって、関数型のアプローチでは現実的な解法が思い浮かばないときに、恰好の避難所になるだろう。Java の呼び
出し方法は第3 章「Clojure からJava を使う」(p. 59)で説明する。
Clojure の、変更可能な状態へのアプローチは、明示的なロックを使わない並行プログラミングを可能にし、Clojure の関数型のコアを補完するも
のとなる。
Clojure は並行プログラミングを簡単にする
Clojure は関数型プログラミングをサポートすることで、スレッドセーフなコードを簡単に書けるようにしている。変更不可能なデータ構造は決し
て変わることが無いので、他のスレッドによってデータが壊されてしまう心配をしなくてよい。
けれどもClojure の並行プログラミングのサポートは、関数型プログラミングだけにとどまらない。変更可能なデータへの参照が必要になったら、
Clojure はそれをソフトウェアトランザクショナルメモリ(STM)で保護してくれる。STMはJava が提供するロックよりも高レベルのアプローチだ
。脆くてバグを持ち込みやすいロックを作る代わりに、共有される状態をトランザクションで保護することができる。プログラムはずっと書きやす
くなるだろう。多くのプログラマはデータベースの経験からトランザクションについてよく理解しているからだ。
例えば次のコードは、実際に動く、スレッドセーフなインメモリのデータベースを作成する。
(def accounts (ref #{}))
(defstruct account :id :balance)
ref 関数はトランザクションによって保護された、現在のデータベースの状態への参照を作成する。更新は簡単だ。次のコードはデータベースに新
たなアカウントを追加する。
(dosync (alter accounts conj (struct account "CLJ" 1000.00)))
dosync は、accounts への更新をトランザクションの中で実行するようにする。コードは自動的にスレッドセーフになる。これはロックよりも簡単
だ。トランザクションを使えば、どういう順序でどのオブジェクトをロックすべきか悩む必要はなくなる。トランザクションによるアプローチはま
た、よくあるケースではロックよりも良い性能を示す。例えば、読み出しがブロックすることがない、といった理由が考えられる。
上に挙げた例は単純すぎるように見えるかもしれないが、このテクニックは汎用性が高いもので、現場の問題を解決するのに使えるのだ。第6 章「
並行プログラム」(p. 157)で、並行プログラミングとClojure のSTM についてより詳しく説明する。
Clojure はJVM を歓迎する
Clojure からJava にアクセスするのはきわめて単純で直接的だ。どんなJavaAPI も直接呼び出すことができる。
(System/getProperties)
-> {java.runtime.name=Java(TM) SE Runtime Environment
... 後略...
Clojure はJava 呼び出しのためにさまざまな構文糖衣を用意している。ここでは細かいことには触れないが(詳しくは3.1「Java の呼び出し」
(p. 60)を参照)、次のコードにおいて、Clojure 版はJava 版よりも括弧もドットも少ないということに注目してほしい。
// Java
"hello".getClass().getProtectionDomain().getCodeSource()
; Clojure
(.. "hello" getClass getProtectionDomain getCodeSource)
Clojure はJava のインタフェースを実装したりJava のクラスを継承したりする簡単な関数を提供している。また、Clojure の関数はすべて
Callable でありRunnable である。したがって、Java のThread のコンストラクタにClojure の無名関数を渡すのは下に示すとおり簡単だ。
(.start (new Thread (fn [] (println "Hello" (Thread/currentThread))))) #<...>というのはClojure がJava のインスタンスを表示する方法だ。Threadがインスタンスのクラス名、Thread[Thread-0,5,main] はインスタン スをtoString した結果である。 (上の例で、新しいスレッドは終了するまで走るけれど、その出力がREPL のプロンプトと混じり合ってしまうことがある。これはClojure の問題 ではなく、単に複数のスレッドが1 つの出力に書き込むときに起こりうることだ。) Java を呼び出すのはこのように簡単なので、Clojure ではJava をLisp っぽいラッパーで隠すのではなく、必要ならJava API を直接呼ぶことを奨 励している。 さて、Clojure を使うべき理由をいくつか見てきたので、いよいよコードを書いてみよう。
| Hello #