なぜRubyは許容可能なLISPなのか

LISPの真実を読んでたら最後に出てきたので、かなり古い記事だけれども、Eric Kidd氏のWhy Ruby is an acceptable LISPを訳してみました。まつもとさんによる反応もあり、そのエントリの中で原文はほぼ要約されています。


一年前、私はRubyに注目してはいたものの、それを無視することにした。RubyPythonほどポピュラーではないし、LISPほど強力というわけでもない。なのに何故気にかけなければならないというのか。

もちろん、これらの評価基準は考えなおすこともできる。もしRubyLISPよりもポピュラーで、Pythonよりも強力だったらどうなるだろうか?*1 それはRubyを興味深いものにするに足るのではないか?

この疑問に答える前に、LISPを強力たらしめているものは何なのかを定義しておくべきだろう。Paul GrahamはLISPの美徳について雄弁に語ったが、ここでは議論のために次の2つに絞ることにしよう。

  1. LISPは濃い(dence)関数型言語である
  2. LISPはプログラム可能なマクロを持っている

結局のところ、Ruby関数型言語とほぼ同等であり、私が考えていたよりも上手くマクロを模倣することが分かった。

RubyLISPよりも濃い関数型言語である

濃い言語というのはソースを難読化することなく、物事を簡明に表現させる。あなたは一目でより多くのプログラムを見渡すことができ、自然そこにはバグの隠れる余地も少なくなる。ある一定のレベルを超えて、プログラムをより”濃く”するための唯一の方法は、より強力な抽象化を使用することだ。

特に強力な抽象化の1つがlambdaだ。 lambdaを使うことで、あなたはその場ですぐに新しい関数を作って、他の関数に渡したり、後で使うために格納することさえできる。
例えば、LISPでリスト中の各々の数を倍にしたいなら、次のように書くだろう:

(mapcar (lambda (n) (* n 2)) mylist)

mapcarはmylistの各要素を変換することによって新しいリストを作りだす。この場合の変換は「nのそれぞれの値について、nに2を掛ける」と読める。JavaScriptではlambdaはfunctionとして書かれる。それはおそらく少しだけLISPより明快だ:

map(function (n) { return n*2 }, mylist)

もちろん、これはあなたがlambdaでできることに関する一つのヒントにすぎない。このようなプログラミングスタイルを支持する言語は関数型言語と呼ばれる。関数をベースとして動作するためだ。実際に、濃い関数型言語は非常に簡潔になりうる。ひとたび関数型言語の読み方を学べば、それはまったく明快なものになるだろう。

どのようにしてRuby関数型プログラミングという点でLISPに匹敵するものになっているのだろうか。Paul Grahamによる規範的な例を考えてみよう。一つのアキュームレータを作るような関数を定義する:

(defun foo (n) (lambda (i) (incf n i)))

このコードはRubyではわずかではあるがより短くなり、その表記はCハッカーにとってより親しみやすいものになる。

def foo(n) lambda {|i| n+=i} end

acc = foo 3
acc.call(1)   # --> 4
acc.call(10)  # --> 14
acc.call(0)   # --> 14

しかし、Rubyの場合、我々がよりタイピング量を節約できるような興味深い特別な場合がある。lambdaを引数に取るような、ごく単純な関数を考えよう。

;; 'fn'を各々の自然数について一回呼ぶ
(defun each-natural-number (fn)
  (loop for n from 1 do (funcall fn n)))

;; 1, 2, 3... と表示
(each-natural-number
 (lambda (n) (format t "~D~%" n)))

しかし、我々はRubyでこれと同じことをより上手くやることができる。yieldを使うことでlambdaやfnを一掃してみよう。

def each_natural_number
  n = 0
  loop { yield n += 1 }
end

each_natural_number {|n| puts n }

そう。yieldは特殊目的のハックだ。それは一つのlambdaを引数に取る関数に対してのみ使える。しかし重度の関数的コードにおいては、yieldは多くの手間を省いてくれる。次の2つを比較して欲しい。

[1,2,3].map {|n| n*n }.reject {|n| n%3==1 }
(remove-if (lambda (n) (= (mod n 3) 1))
           (mapcar (lambda (n) (* n n))
                   '(1 2 3)))

大きなプログラムでは、この差が積み重なってくる。(LISPに対する弁護としては、lambdaをより簡潔にするリーダーマクロを書くことが可能だということだ。だがそういうのは稀だ。)

Rubyはあなたがマクロでしたいことの約80%は与えてくれる

LISPハッカーはこう言うだろう。「lambdaの良い構文があるのはわかった。でもマクロについてはどうか?」これは良い質問だ。LISPのマクロは次のような機能を持っている。

  1. コンパイラの中で走る
  2. カスタマイズされた構文を生のLISPに変換する

LISPマクロの最も一般的な使用法は沢山のlambdaをタイプするのを避けることだ。

(defmacro with-each-natural-number (n expr)
  `(each-natural-number (lambda (,n) ,expr)))

(with-each-natural-number n
  (format t "~D~%" n))

defmacroは一つのリストを引数として取り、そして別のリストを返す関数を定義する。この例の中で我々のマクロは、コンパイラがwith-each-natural-numberを見る度に呼ばれる。このマクロ定義では、nとexprをテンプレートに埋め込み、素早くリストを構築するために、LISPのバッククォート構文を利用している。それから変換されたリストは再びコンパイラに渡される。

もちろん、このマクロはRubyにおいては役に立たない。Rubyでは生じない問題の周りで働くマクロだからだ。

その次に最も一般的なLISPマクロの使われ方は、何かを定義するためのミニ言語を作ることだ。

;; 架空のフレームワーク"LISP on Rails"を利用して、
;; データベースへのバインディングを生成する
(defmodel <order> ()
  (belongs-to <customer>)
  (has-many <item> :dependent? t))

Ruby on Railsを使えば、こう書くことができる。

class Order < ActiveRecord::Base
  belongs_to :customer
  has_many :items, :dependent => true
end

ここで、belongs_toはクラス関数だ。これが呼ばれたとき、メンバ関数の束をOrderに追加する。実装はかなり汚ないが、インターフェースはよくできている。

あらゆるマクロに似た機能に対する現実のテストは、 どれくらいの頻度でミニ言語を作るのに使われるかということだ。テストに対するRubyのスコアはこうだ: Railsに加え、RakeMakefileを書くのに使われる)、Needleコンポーネントを接続するために)、OptionParserコマンドラインオプションをパースするために)、DLC言語APIと会話するために)、そしてその他にも数え切れないほどにある。RubyプログラマはなんでもRubyで書くのだ。

もちろん、簡単にRubyに輸出できないような高度なLISPマクロは沢山ある。特に、ミニ言語を本当にコンパイルするようなマクロはRubyではまだ出てきていない。(Ryan Davisはこの方向でParseTreeRubyInlineと、いくつかの有望な仕事をしている。私はそれを理解し次第関連するテクニックについて書くつもりだ。)

Rubyのライブラリ、コミュニティ、勢いは良好だ

それで、もしLISPが依然としてRubyよりも強力なら、何故LISPを使わないのか? LISPでプログラミングすることに対する典型的な異議は:

  1. 十分なライブラリが無い
  2. LISPプログラマを集められない
  3. LISPは過去20年どこか分からない所に行ってしまった

これらは抗し難いほどの異議ではないが、確かに一考に値する。

はるか昔、Common Lispの標準ライブラリは巨大なものだと考えられていた。しかし今日では、それは痛々しいまでに小さい。Javaのマニュアルは壁を埋め尽していて、PerlCPANアーカイブはあなたが想像し得るいかなるモジュールでも持っている。それに対してCommon Lispはネットワークと語る標準的な方法さえ持っていないのだ。

同様に、LISPプログラマーは稀少である。もしあなたがボストンの周辺にいるなら、ほとんど魔法に近い仕事をする灰色のハッカー達の小さなたまり場があるが、他の場所では好奇心をそそられた若いハッカーが薄く散在しているのみだ。しかしLISPは常に少数派の言語として存在してきた。

一方でRubyは急速に人気を得てきている。大きな推進力を与えているのはRailsのようだ。Railsの成長は2004年の暮れから始まった。もしあなたが会社を立ち上げようとするなら、可能性のある従業員は皆Rails愛好者だと言っても過言ではない。RailsはすぐにありふれたWEBコンサルティングへ浸透していき、いずれは大きなビジネスとなっていくだろう。

Rubyもまた、良い標準ライブラリと追加ライブラリの豊富なアーカイブを開発するのに十分な時間を費してきた。もしあなたがWEBページをダウンロードしたり、RSSをパースしたり、グラフを生成したり、SOAP APIを呼ぶことを必要としているなら、それらは全て用意されているのだ。

強力な言語とポピュラーな言語との間で選択を迫られたとき、強力なものを選ぶことが素晴しい意味を成すことがあるかもしれない。
しかし、もし強力さの点で差が小さいのなら、ポピュラーであることはもろもろの点で好ましい利点を持つ。2005年には、私はRubyよりもLISPを選ぶ前に、じっくり考えることにした。最適化されたコード、あるいは完全に独立したコンパイラとして作用するマクロが必要なときのみ、LISPを選ぶように。