Lispの力の源泉 - 同図像性
[ PR ]
上の写真は、"On Lisp"や"ハッカーと画家"などの著者ポール・グレアムです。
ポール・グレアムは、自身のブログでLispに関する記事を多く執筆しており、現在はLisp方言のArcに力を入れているようです。
以下の内容は、このポール・グレアムのLispに関する知見を元に自分なりにまとめたものです。
Lispのすごさとは
今まで自分は、"Lispはすごい" と思いながらも、実は半信半疑な部分がありました。
ところが、意外なところでそれがわかったのは、プログラミングClojure 第2版 を読んだときでした。
以下、その文章の引用です。
言語の中からその言語を変えてしまうというのはLispに特有の利点だ。この利点は次に挙げるようなさまざまな側面をもつ。
Lispは同図像性を持つ(homoiconic)言語だ。すなわち、LispのコードはLispのデータでもある。これによって、「プログラムを生成するプログラム」というのが簡単に書けるようになる。
また、言語のすべてが、常にそこにある。Paul Graham はエッセイ「技術野郎の復習」で、これがどうしてそんなに強力なのかを説明している。
同図像性とは
脚注によると、
同図像性とは、あるプログラミング言語において、プログラム自体がプログラミング言語自身のデータ構造で表現されていること (https://en.wikipedia.org/wiki/Homoiconicity)
「技術野郎の復習(Revenge of the Nerds)」の原文は、http://paulgraham.com/icad.html にあり、日本語訳はhttp://practical-scheme.net/trans/icad-j.htmlである
ということが書いてあったので、「技術野郎の復習」の訳文を読んだところ、Lispの本質である同図像性が、Lispの本質を成しているということがすぐ理解出来ました。
つまりLispの本質は、Lispの自体がLispのデータ構造により表せるということにあります。
同図像性の解説
わかりやすく説明できる自信がありませんが、自分なりに説明してみます。
例えば、Javaにはclassとinterfaceという構文があります。
class Calc {
int double(int x){
return x*2;
}
}
ある程度プログラミングをしたことがある方はすぐわかると思いますが、これは構文です。つまり定型句です。どんなにこの構文が面倒でも、クラスを定義するにはこの方法しかありません。
独自の構文がある程度定義できるScalaにおいても、より定型句が短くなっているものの、別の方法を使うことはできません。ましてや自分でこの構文を変えることはできません。
しかしLispでは、マクロを使って自由な構文に変えることが可能です。これは、Lispの構文自体が「リスト」というLispのデータ構造であるために、マクロという名のメタプログラミングを使って、あるS式(リスト)を別のS式(リスト)に変えることが容易にできるからです。
これは、Lispの持つ同図像性により可能になるものです。
実は、Scalaもマクロが使えるのですが、Lispのように同図像性を持たないため、構文木を表すコードは複雑になり、マクロを表すコードはLispよりはるかに長くなってしまいます。
Lispの強み
この辺りでなんとなく分かってきたと思います。
では、Lispの強みについて再確認したいと思うのですが、何だと思いますか?
このことについて、ポール・グレアムはLispとFortranを挙げて説明しています。Fortranは今の言語にくらべて、非常に低レベルであるが、Lispは今も色褪せていないと。
低レベルであるというのは、抽象度が低いという事を表しています。つまり、今日の新しく出来ている言語はどんどん抽象度が高くなっているということです。ポール・グレアムはこれを「Lispに近づいている」と言っています。
つまりLispの強力さというのは、高い抽象度であると言えます。
Lispの抽象度の高さは、例えば高階関数やマクロによって説明されることが多いですが、本質である同図像性を使って説明してみようと思います。
Lispの抽象性と同図像性
例えばJavaを例をとして説明してみます。
Javaに用意されている抽象の手段は、関数やクラスです。ですが、関数やクラスをさらに抽象する手段は、(あるにはありますが)用意されていません。
例えば、次の処理はこれ以上うまく抽象化できません:
int double(int x){
return x*2;
}
double(double(4));
一見できるように感じます。例えば次のようにすればいいような気がします:
int double(int x){
return x*2;
}
int quadruple(int x){
return double(double(x));
}
quadruple(4);
確かに抽象化されていますが、これではdoubleを2回繰り返していることに変わりない上に、quadrupleはdoubleを使って定義しているため、汎用性がありません。これでは抽象的とはあまりいえません。
先程登場したScalaには、高階関数という関数を抽象化する方法があります。例えば前の例を抽象化すると、次のようになります。
def double(x:Int)= {
x*2
}
def twice(f:Int=>Int) = {
f compose f
}
(twice(double))(4)
関数twiceは、Intを引数にとってIntを返す関数を引数としています。これが高階関数です。先ほどの例のようにfを2回繰り返してはいますが、このtwiceはどんな Int=>Int の関数でも引数に取れるという汎用性があります。これで先程より抽象化することができました。
しかしScalaでも抽象化できないものがあります。先程も述べたように、クラスの定義はそれ以上抽象化できません。
当たり前といえばそうですが、同じようなクラスをいくつも定義する際には、どうしても何回も同じコードを書く必要があり不便です。
Lispでは、クラス定義を抽象化することができます。それだけではなく、すべての構文を抽象化することができ、その新しい構文自体も抽象化することができます。
この理由は、Lispの構文は、Lispのデータ構造である「リスト」だからです。これが同図像性を持つということです。
構文がLispのデータ構造で出来ているということは、リストをS式に「コンパイルする」ための仕組みを作れば、既存の構文をより抽象化することができます。これがマクロであり、このことがLispの持つ高い抽象性のすべてを表しています。
これを支えているのは、ジョン・マッカーシーが発表したLispの理論を、プログラミングLispに変えた、大学院生のスティーブ・ラッセルが発明した"eval"です。
同時に、「技術野郎の復習」でポール・グレアムが書いていることですが、S式はリストなので、S式はデータです。つまり、プログラム同士はS式を使ってやりとりをすることができます。最近これはXMLとして再発明された、とポール・グレアムは書いています。
これが、Lispの力である「高度な抽象化」の源が、同図像性であるということです。
ゆえにLispは、あの括弧だらけのS式のおかげで、特定の構文を持たず、ゆえに高度な抽象化が可能になり、高い生産性を持つことができたということですね。