マクロの使用法

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