Cyan のマクロは単なるパターンマッチングではありません。 Cyan におけるマクロは、Lisp のマクロと同等のパワーを持ちます。 通常は準クオートを使って記述しますが、それにとらわれない柔軟な記述が可能です。
マクロが呼び出される際、引数は評価されずにそのまま渡されます。 そして、その実行結果が呼び出し元に挿入され、もう一度評価されます。 結果的に、マクロはコードの置き換えを行っていることになります。
ここでは、実際にマクロの定義を見ていき、マクロの使用法について説明します。
まずは、準クオートを使った単純な置き換えです。下のコードを見てください。
mac(while)^(test, body): `loop: if(!?test): break() begin(?body) |
これは、初期化ファイル群の中の control.cy にある、while マクロの定義です。
while(i < 10): say(i) i++ |
というコードを、
loop: if(!(i < 10)): break() begin: say(i) i++ |
に置き換えていると考えることができます。 この場合、展開後のコードをそのまま記述して準クオートし、 そのうち引数で与えられた部分をアンクオートすればマクロの完成です。 展開後のコードの形が見えるので、マクロの動作がわかりやすいのが特徴です。
次は、条件によって生成されるコードが変わるマクロです。
mac(def)^(name, func): if(name.parent == Messenger): `(?name = method(?func)) else: `(?name = ?func) |
これは、初期化ファイル群の中の definition.cy にある、def マクロの定義です。
def(f)^(x, y): say(x + y) def(A.g)^(x): say(x ** 2) |
といったコードを、
f = ^(x, y): say(x + y) A.g = method^(x): say(x ** 2) |
というように置き換えます。 マクロの本体は関数であるため、このように通常の関数と同じような処理が書けます。
次は、準クオート以外の表現を使ったマクロです。
mac(class)^(name, func): parent := [] if(func.params.required.null?()): parent = 'Object else: parent = func.params[0] list := func.body.list.map^(exp): given(exp.parent): when(Porter): if(exp.callee == 'def || exp.callee == 'mac): update!(exp.args.list)^(arg_list): [Messenger.new(name, arg_list.car()) | arg_list.cdr()] when(Messenger): if(exp.message == ('x.(=)).message): exp.receiver = Quote.new(Messenger.new(name, exp.receiver.value)) exp `begin: ?name = (?parent).child() ?*list |
これは、初期化ファイル群の中の class.cy にある、class マクロの定義です。 使い方は、 class.cy におけるグローバル変数の説明や、 サンプルを見てください。
ここでは、与えられた関数のブロックに対し、それぞれの式を map メソッドで 加工しています。 その後、スプライシングを使って、式のリストをブロックにつなぎ合わせています。
一見複雑そうに見えますが、書くのにはほとんど苦労していません。 加工前のコードと加工後のコードを見比べながら見ていくと、 やっていることもさほど難しくありません。 ただ、これだけを見て内容を理解するのは難しいかもしれません。
最後に、マクロと関数を組み合わせたものを紹介します。
mac(cond)^(body): `begin: else := true ?cond_body(body.list) def(cond_body)^(list): if(list.null?()): [] else: exp := list.car() `if(?exp.callee): begin(?exp.args.list.car()) else: ?cond_body(list.cdr()) |
これは、初期化ファイル群の中の control.cy にある、cond マクロの定義です。
cond: (x > 0): say("x > 0") (x < 0): say("x < ") else: say("x == 0") |
というコードを、
begin: else := true if(x > 0): begin: say("x > 0") else: if(x < 0): begin: say("x < 0") else: if(else): begin: say("x == 0") else: [] |
に置き換えます。 再帰的な処理を伴うため、コードの作成を別の関数に任せ、 再帰を用いて記述しています。
このように、Cyan のマクロは、 様々なテクニックを駆使して柔軟に記述することができます。 可視性が低くなることがあるので、過度な使用は避けるべきですが、 とても強力な機能であることは確かです。