プログラミング言語の拡張可能性は様々である。
(1)すべてを拡張可能
拡張可能性の高い言語は、新しい制御構文や定義構文を追加できる。LISPやRuby、Perl6がこのカテゴリに入る。
この種のプログラミング言語は内部DSLを定義するのが得意だ。その結果、DSL文化が発達する。たとえばRubyではRakefileのように優れたDSLを使ったツールが数多く作られた。
以下はRakefileの例である。各種方言のあるMakefileよりも柔軟でmakeごとの互換性に悩まされることもなく、そしてmakeに慣れていれば読むのも難しくない。素晴らしいツールである。
file "prog" => ["a.o", "b.o"] do |t|
sh "cc -o #{t.name} #{t.prerequisites.join(' ')}"
end
欠点は、その柔軟性ゆえの可読性の低下である。言語組み込みの機能なのかそうでないのかが非常にわかりにくい。これにメタプログラミングが加わると非常にメンテナンス性の悪いプロダクトになってしまう。
(2)セマンティクスを拡張可能
構文の拡張はできなくとも、セマンティクスの拡張、つまり言語要素のオーバーロードをサポートしている言語もある。PerlやC++がこれにあたる。オーバーロードは強力で、時には可読性を上げる。たとえば、BigIntの演算はメソッドで行うよりも四則演算子を適用したほうが可読性が高い。
欠点は1と同じく、乱用すると可読性が下がることである。これの典型は、 boost::xpressive が挙げられよう。文字列リテラルとして正規表現を書くよりも演算子を使ったほうがコンパイル時に構文チェックができるし、コンパイル時にいくらか処理を済ませておくことで実行時のコンパイル時間も短縮できる。たしかに素晴らしい。しかし、読むのは至難の業である。
using namespace boost::xpressive;
// "static regex"
// /A([1-9]*|[a-z]*)A/ とおなじ
sregex r = 'A' >> (*range('1','9') | *range('a','z')) >> 'A';
(3)言語要素は拡張不可能
構文の拡張もオーバーロードもなく、新しく定義できのは新しい関数・メソッドや構造体・クラスのみの言語もある。CやJavaがこれにあたる。この種の言語は誰が書いても同じにようになりやすい。その結果、ある程度のスキルがあれば可読性は保たれる。(1)や(2)が持っていたような、「スキルが高いゆえに可読性の低いコードになる」という欠点がない。
欠点はコード量(1)や(2)に比べて多くなること。
結論
実際には綺麗にこの三つに分けられるとは限らない。JavaScriptは(2)と(3)であるし、Cは基本的には(3)だがプリプロセッサを使ってかなり強力に拡張することもできる。
さて、#NextLanguage はどこを目指すべきか。これはプロジェクトごとに正解が異なる議論であると思う。故に、まだ結論は出さない。