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

変数? なにそれおいしいの?

やってることは簡単。

  1. 配列を次のルールで写像:配列以外ならそのまま返し、配列なら再帰的に評価
  2. self.sendに写像した配列を渡すことでメソッドを呼び出す。英語で言うと動的ディスパッチ

次のように 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でもできるかも。