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 のマクロは、 様々なテクニックを駆使して柔軟に記述することができます。 可視性が低くなることがあるので、過度な使用は避けるべきですが、 とても強力な機能であることは確かです。