###マクロとは何?
マクロとは,関数とあまり違いがないように見えますが,色々違います.それをこれから見ていきます.
###マクロで何ができるの?
コンパイル時評価,新しいシンタックスの作成,コード記述量の短縮ができます.
##事始め
Nemerleの現在の仕様では,マクロは他dllのもののみを使うことができ,同一dllでマクロを作成・利用はできないので,プロジェクトソリューションにマクロライブラリプロジェクトを追加して,メインプロジェクトの参照に追加する必要があります.
メインプロジェクトにマクロライブラリの参照を追加し終わったら,早速,マクロライブラリの方にマクロを書いていきます.マクロはstatic class
(module
)・class
に書くことはできません.namespace
の下に書かなければいけません.マクロプロジェクトを作れば,適切な場所にマクロができていると思うので,そこまで思慮しなくてもいいと思います.
##コンパイル時評価のマクロ
では,マクロを書きます.
public macro HogeMacro() {
WriteLine("Hello, Macro!");
<[()]>;
}
<[()]>;
はあとで詳しい話をしますが,MacroはPExpr
型の値(?)を必ず返すので,必要です.
そうしたら,メインプロジェクトからマクロを呼び出すようにします.using
でマクロの名前環境をユージングします.後々説明しますが,この場合は,_ =
は不要です.
using <MacroNamespace>;
Main() : void {
HogeMacro();
_ = ReadLine();
}
実行してみましょう.しかし,何も表示されません.しかし,VSの「出力ウィンドウ」の「ビルド」を見ると,このような感じに出力されています.
------ ビルド開始: プロジェクト:Macro Project, 構成:Debug Any CPU ------
Macro Project -> DLL Name
ビルドに成功しました。
Time Elapsed 0:00:01.29
------ ビルド開始: プロジェクト:Main Project, 構成:Debug Any CPU ------
Hello, Macro!
Main Project -> EXE Name
ビルドに成功しました。
Time Elapsed 0:00:00.90
========== ビルド: 正常終了または最新の状態 2、失敗 0、スキップ 0 ==========
MainProjectのビルド時に"Hello, Macro!"と出力されているのがわかるでしょうか.このように,マクロ内でのコードはコンパイル時に呼び出されたときに評価されます.では,マクロ内で実行時評価をするにはどうすればいいでしょうか.
##PExpr
<[]>
でPExpr型の値を表現します.PExpr型は簡単に言うと式木(Expression型
)のようなもので,処理を表します.どのように処理を記述するか,ですが,普通に書けばよろしいです.
<[
Console.WriteLine("TEST!");
def n = 12;
12;
]>
返し値も普通に表現できます.
空(void
)のPExprは,()
で表現すれば,おkです.
<[()]>
##マクロの返すPExprの振る舞い
マクロは必ずPExprを返さなければなりません.そして,そのPExprは,呼び出し元のコードに置換されます.どういうことかを説明すると,
public macro RandMacro() {
<[
def r = Random().NextDouble();
Console.WriteLine(r);
r;
]>
}
public void Main() : void {
def n = RandMacro();
}
これが,コンパイル時に
public void Main() : void {
def n = {
def r = Random().NextDouble();
Console.WriteLine(r);
r;
}
}
のように,RandMacro()がRandMacroの返り値に置換されます.(実際に,ILSpy等で確認してみるのもいいでしょう.)このように,返し値のPExpr
に処理の記述をすることによって,呼び出し元のコードを変更,すなわち,実行時評価することができます.
コンパイル時にソースコードを組み替えたり,予め計算したりして,コードの視認性を上げる・処理を高速化させる,これがマクロです.
##<[]>の中から<[]>外の変数を使う
PExpr
の中からPExpr
外の変数を利用するには,$
を変数名の前に付ける必要があります.
public macro HogeMacro(d) {
def s = System.Math.Sqrt(d);
<[$s]>;
}
このようなマクロを書くことによって,Sqrtメソッド
は,コンパイル時に評価されるので,実行時の計算数が減ります.
##引数
まず,コンパイル時評価でやってみましょう.
public macro HogeMacro(d : double) {
System.Math.Sqrt(d) |> WriteLine;
<[()]>;
}
public void Main() : void {
HogeMacro(16.0);
}
コンパイル時に,"4"と出力されるでしょう.
public void Main() : void {
def n : double = 12
HogeMacro(n);
}
上のコードをコンパイルしてみましょう.コンパイルエラーが出ます.コンパイル時評価で使われる引数は直接コンパイル時に解釈できる定数を代入しなければならないようです.
次に,実行時評価でやってみます.
public macro HogeMacro(d) {
<[System.Math.Sqrt($d)]>;
}
引数でも$
を付けます.
実行時評価は,引数が,コンパイル時に確定できなくても大丈夫です.
(あんまり,筆者もココらへん把握しきれてない)
#シンタックスの拡張
シンタックスの拡張ではsyntax
構文を使います.
public macro newifMacro(cond, e1, e2)
syntax ("newif", "(",cond, ")", e1, "else", e2){
<[
match($cond) {
| true => $e1
| false => $e2
}
]>
}
newif(condition) {
code1
} else {
code2
}
if文の例ですが,見てわかるように,とてもわかりやすく記述することができます.
'{}'がsyntax
の中に定義されてないから,複数行かけないんじゃないの?と思われそうですが,前述したような,「ブロック」という機能があるので,特記しなくても,複数行でも一行でも大丈夫です.
##Optional
newif()
WriteLine("True")
else
WriteLine("False")
上のように,条件の括弧の中を省略した場合,自動的に,Trueのコードが実行されるようにします.そんな時に,Optionalを使います.
public macro newifMacro(cond, e1, e2)
syntax ("newif", "(", Optional(cond), ")", e1, "else", e2){
def cond = if(cond == null) true else cond;
<[
match($cond) {
| true => $e1
| false => $e2
}
]>;
}
省略された場合,その引数はnull
になります.これは,コンパイル時に決まるので,コンパイル時評価で分岐ができます.上記の例では,cond
がnull
だったときに,cond
をtrue
に再定義しています.
(defで,変数の再定義ができ,その変数の型等も変えることができます.)
##マクロ名
今までの例では,newif
構文のマクロ名をnewifMacro
としてきましたが,カッコ悪いので,newif
という名前にしましょう.といっても,そのまま書くと,コンパイラは構文なのかマクロ名なのか分からずエラーを出すので,構文と被ってしまう時には,先頭に@
を付けます.これは,マクロに限った話ではなく,関数や変数の定義の時にも使えます.
##構文の重複
今までで定義した,newif文は,newif ( <cond> ) { <e1> } else { <e2> }
という,else
まで記述しなければいけない面倒なif文です.なので,else
のいらないパターンも作ります.
macro newifMacroWithoutElse(cond, e)
syntax("newif", "(", cond, ")", e) {
<[
match(cond) {
| true => e;
}
]>;
}
普通に書けば大丈夫です.
#〆
今回は,マクロの基礎・構文の拡張の基礎について紹介しました.
次回からはクラス・メソッドを拡張するマクロについても紹介しますが,どんな感じに説明するか考え中です...