ISLISPを使うべきでないたった1つの理由
突如としてライフハック風なわけだが ∧_∧ ∧_∧ (´<_` ) 風味だけだ ( ´_ゝ`) / ⌒i 中身はないぞ / \ | | / / ̄ ̄ ̄ ̄/ | __(__ニつ/ FMV / .| .|____ \/____/ (u ⊃
LISPマクロについての今更感あふれる話(知ってる人は飛ばしておk)
LISPと同図像性
LISPマクロは他の言語に類を見ない極めて協力な機能を備えている,LISPはマクロによって他の言語と決定的に違った言語である,と言われます.
LISPのマクロとは何がそんなに特別なのでしょうか.それは,「カッコ大杉.修正されるね」としばしばネタになる,LISPの構文と深い関係があります.
LISPは同図像性を持つ言語です.これは,LISPのソースコードが,その言語で操作可能なデータとして書かれるということを意味しています.LISPは,関数呼び出しは,関数を第一要素,引数を残りの要素とするリストとして書かれます.
関数が第一要素のリストというのは,Rubyでたとえれば,
puts("hello")
というコードが,
(puts "hello")
こう書かれるということです.さらに,これをRubyのデータ構造で表して,
[:puts, "hello"]
という風に書かれることになります.
関数呼び出しの引数が別の関数呼び出しであってもよいように,これはネストにすることができます.
[:puts, [:+, 1, 1]]
これがLISPの文法です.異質に感じられるかもしれませんが,大変便利なものです.
コードと評価
LISPのソースコードは,まず「読み取り器(リーダ)」にシンボルのリストとして読まれ,そのシンボルのリストが「評価器」によって評価され,何らかの値を返す,という形で実行されていきます.
(fun a b)
というプログラムは,
(list 'fun 'a 'b) ; あるいは '(fun a b)
というデータとして読まれたあと,このリストが関数呼び出しを表していると評価器によって判断されて,関数が呼び出され,評価されます.
Rubyであれば,irbを想像してみてください.
'[:puts, "hello", :world]'
という文字列が読み込まれて,
[:puts, "hello", :world]
というオブジェクトが返されます.Rubyの評価はこれで終わりですが,LISPにおいては,先述の通り,このオブジェクトがコードです.
"hello"という文字列は評価されると再び "hello" という文字列を返します.:worldというシンボルは,LISPの考え方からすると,変数です.シンボルに対応する値として評価されます.最後に,:putsがメソッドとして評価され,そのメソッドに"hello"と:worldに対応した値が渡されます.
つまるところ,このコードはこのような意味になるわけです.
puts("hello", world)
これは大変ややこしい話のようですが,普通のプログラミング言語も,まず文字列が意味のある単位(トークン)に分割され,トークンの列が木構造のデータ(構文木)に整理されます.そしてプログラムが実行されたりネイティブコードを吐き出すというのは,この構文木を辿りながら行われるわけです.
LISPというのは構文木を表現するのにリストを使い,その構文木をつくる前段階,プログラムのソースすら,リストリテラルを使って,特別な記法を使わない,という,大変シンプルな言語です.
LISPマクロはなぜすごい
LISPのプログラムは,LISPのデータです.であれば,LISPのプログラムを使って,LISPのプログラムを書くことができるはずです.要するに,Arrayを出力するようなコードを書けば,それがそのままコードになります.
これは,たとえば「Rubyは正規表現を大変便利に使える言語だから,Rubyプログラムを生成するRubyプログラムを書けるはずだ」という話と少し似ていて,少し違っています.
短い記法,プログラムのためではなく文書化のための記法,あるいはより自然に見える記法から,プログラムを生成しようという発想は同じです.
しかし,LISPが操作するのは文字列ではなく,LISPのデータ,リストです.Rubyで例えると,これは「ArrayとHashで擬似コードを書いて,それをevalしてデータ構造を得たあと,それを操作してプログラムを書き出す」というようなことです.文字列を操作するよりずっと複雑なことができます.
RubyがArrayやHashにたくさんの便利な機能を備えているように,LISPはリスト処理のための関数を山ほど揃えていますから,たいていのマクロは簡単に書くことができますし,自分で定義した関数でプログラムを生成してやってもよいので,大変パワフルです.
さらに,マクロによるコードの生成は,コンパイル時に行われます.リーダが読んだリストを,マクロで展開して,正しいLISPコードになった所で,さらに機械語やバイトコードにコンパイルすることができます.つまり,(事前にコンパイルしておけば)マクロでの変換は実行時にコストが掛からないということです.*1
一番いいことには,ArrayやHashの記法と違って,LISPにはカンマがいりません (^-^ )
このような理由から,LISPマクロはスゴイと言われるわけです.
本題:ISLISPを使うべきでないたった1つの理由
ISLISP is 何
ISLISPというLISPがあります.ISOのLISPです.
ANSI Common LISPがそれまでに乱立していたLISPの最小公倍数的な仕様であるのに対し,ISLISPは最大公約数的なLISPです.その仕様は,わずか100ページほどです.しかしその中身は,CLOS相当のオブジェクト指向機能とコンディションシステム(エラー処理システム)を備えた,たいへん意欲的なものです.
Schemeと同じく小さな仕様を持つLISP.しかしSchemeとはまた方向性の違うLISP.
しかし,このLISPには一つ,大きな悲しみがあるのです.
『8. マクロ』
マクロは,実行準備時に展開される,いかなる実行時情報も使えない.
仕様にあるこの一文が悲しみです.「実行時情報」が使えないということは,つまり,自分で定義した関数によるマクロの展開は行えない,ということです.なぜなら,関数が定義されるのは,実行時だからです.
ユーザ定義の関数が使えないのでは,いくらLISPがリスト操作を得意とするといっても,マクロを書くのは大変だし,メンテナンスも難しくなります.
たとえば,こんなコードがあったとします.
(defun hello-extr (arg) `(format t "Hello ~A!" ,arg)) (defmacro hello (arg) (hello-extr (arg)))
これは,Common Lispではコンパイルを通りません.なぜなら,helloマクロが使っているhello-extr関数が,マクロが展開されるコンパイル時,使用できないからです.
これを回避する2つの方法があります.一つはインタープリターにこのコードを実行させることです.S式を1つずつ読んで評価していけば,すでに定義してある関数はマクロの展開に使うことができます.
あるいは,hello-extrがコンパイル時に評価されるようにするという手もあります.
(compile-toplevel (:compile-toplevel) (defun hello-extr (arg) `(format t "Hello ~A!" ,arg)) ) (defmacro hello (arg) (hello-extr (arg)))
hello-extrがコンパイル時に使えれば,マクロの展開にも困りません.
ISLISPでは,どちらの方法も使えません.規格によって否定されているからです.これがISLISPを使うべきでないたった1つの理由であり,もしあなたがLISPを書きたいなら,おそらくそれはLISPマクロを書きたいということでしょうから,Common LISPを使うべきです.
真実
そうか.じゃあCommon LISPを使おう ライフハックを即座に実行 流石だな俺 ∧_∧ ∧_∧ (´<_` ) まあ,そもそもISLISP処理系が全然ないから,使いたくても使えないんだがな ( ´_ゝ`) / ⌒i / \ | | さらに見つけた処理系は普通にユーザ定義の関数が使えた / / ̄ ̄ ̄ ̄/ | 要するにこの記事は全文無意味とほぼ同じだ __(__ニつ/ FMV / .| .|____ \/____/ (u ⊃ \\\ (⌒\ ∧_∧ \ ヽヽ( ´_ゝ`) (mJ ⌒\ ノ ∩兄 / / ( | .|∧_∧ OK,兄者,正直すまんかった /\丿 | ( ) 5000円で仕様書(JISから出ている翻訳版)を買ったことに免じて許せ (___へ_ノ ゝ__ノ 馬鹿なことに金を使ったものだな どうするんだその仕様書 落書き帳にでもするのか ∧_∧ ∧_∧ (´<_` ) SchemeじゃないLISPを実装するときにでも ( ´_ゝ`) / ⌒i 参考にするよ.多分 / \ | | / / ̄ ̄ ̄ ̄/ | __(__ニつ/ FMV / .| .|____ \/____/ (u ⊃
*1:コンパイルと聞いて疑問に思った方もおられるかもしれませんが,Common LISPのコードはコンパイルして実行することもできますし,インタープリターで実行させることもできます.