Rubyで同図像性っぽいことをする
思いついた
あるプログラミング言語が、そのプログラミング言語が操作するデータを用いて記述される場合、その言語は同図像性を持つ、というらしい。
へっぽこRuby使いである私は、それがRubyでもできないかな? というか、できるんじゃないかな? と思った。
実際にやってみた。
ソースの表現
LISPはシンボルとリストによりソースを表現するが、Rubyの場合、それに相当するのは配列とキーワードシンボルなので、それを使うとこんなふうにソースを書くことになる。
# puts(1, 2, sum(3, 4)) に相当 source = <
読取器と評価器
ソースを読んでRubyのデータ構造に変換する読み取り器、それを評価する評価器は、こんな感じに書ける。
def reader source eval(source) end def evaluate sexp self.send( sexp.map{|exp| if exp.is_a?(Array) then evaluate(exp) else exp end }) end
変数? なにそれおいしいの?
やってることは簡単。
次のように sum メソッドを定義しておけば、[:sum, 3, 4]がsum(3,4)扱いなので、正しく動く。
def sum(*args) args.inject(:+) end
マクロ
さらに、読み取った後のソースはRubyの配列なので、Rubyの配列操作メソッドで好きなように改変できる。これを利用するとプログラムを書くプログラムが書ける。英語で言うとマクロ。
マクロ展開関数を次のように書いてみる。ここではsayというマクロが定義されている。これは、 [:say, arg, ...]を [:print, arg, ..., "\n"]に変換する。(まあ、そんなものは本当は関数で書けばいいんだけども)
def expand_macro(sexp) if sexp[0] == :say return [:print, *sexp[1..-2], "\n"] else return sexp end end
評価機にマクロ展開ステップを追加してみる。
def evaluate sexp # 新たに追加されたマクロ展開のステップ expanded_sexp = expand_macro(sexp) self.send( *expanded_sexp.map{|exp| if exp.is_a?(Array) then evaluate(exp) else exp end }) end
これで、次のソースが動く。
# puts(1, 2, sum(3, 4)) に相当 source = <
ソースの全体
def sum(*args) args.inject(:+) end def expand_macro(sexp) if sexp[0] == :say return [:print, *sexp[1..-2], "\n"] else return sexp end end source = <
やってみた感想
同図像性があるとマクロが使えて便利。というか、マクロを書くのに慣れ親しんだ配列のメソッドを使えて便利。
黒魔術っぽくも見えるが、『達人プログラマー』にも、擬似コードから実際のコードを生成するテクニックが載っていたし、黒魔術ではない。島は移動しない。
動的ディスパッチしてるだけで何も新しくないし黒くもないと思ったあなたは正しい。
キーワードシンボルのためのコロンや、要素を区切るためのカンマをいちいち書くのは実にめんどくさい。その点、シンボルもオブジェクトで、空白で区切りさえすればリストを書けるLISPは完璧で幸福な言語である。
readerメソッドをいじればリーダマクロが書ける。正規表現もあるしきっとなんとかなる。
Pythonでもできるかも。