Clojureでマクロ を学習中

7つの言語 7つの世界

7つの言語 7つの世界

プログラミングClojure

プログラミングClojure

マクロを勉強中。 unlessをマクロで定義する簡単な例。その後、テンプレートエンジンを使うような感じで、プログラムを書くプログラムの例が進み、マクロの分類の話が出てきた。分類の話は、On Lispをちょっと思い出し、軽く7章に目を通した。


unlessについて。関数にできなくて、マクロに出来ることの1つとしては、引数の評価を遅らせられるか否かがある。unlessはその例になっている。

testを評価してfalseのときのみ、bodyが評価されるのが、unlessの期待する振る舞いとなる。

(defmacro unless [test body]
  (list 'if test nil body))
(unless false  (println "print"))        ; #=> "print"
(unless true  (println "not print"))   ; # => 

下2行の実行結果になるのは、マクロ定義のところで、unlessのリストを、 if のリストに展開し直すようになっているため。


Ruby目線でマクロをいろいろ考えたが、マクロしか出来ないことあるんだなと、再認識。

Rubyのブロックも評価を遅らせられる点では似ている。が、マクロと違って、いろいろ制約(ブロック要素は1つ)があるので、my_unlessの構文をつくることはできないはず。(my_unlessをメソッド定義するには、testとbodyの2つブロック要素を渡し、testを評価して falseのときのみ、 bodyを評価したいが、渡せるブロックは1つ)。

ERBの様なテンプレートを使って生成したコードを eval すれば、似たことが出来るんじゃねと思ったが、それでは、呼び出し側の引数 testと body を文字列で渡すことになるのでいまいち。

無名の関数オブジェクトを test, bodyとして、引数に渡す時点では評価せず。。。、と考えたが、これも呼び出し側が,少々不格好になる。


関数やメソッドでは、この新しい構文をうまく、つくれない。ふむふむ。

def my_unless(test, body)
  if test.call()
  else
    body.call()
  end
end

my_unless ->{ false }, -> do
  puts "print HOGE"
end

unless false
  puts "hogehoge"
end


ちなみに、Rubyの場合は既に unless、Clojureには、when-notの構文があらかじめ用意されている。
「unless」の構文で マクロのことを悩む必要はない。