マクロ(macro)とは、定められた形式の書式を別の定められた書式へと変更する仕組みのことです。
一般のエディタにも似たような機能があり、例えば「Java」という文字列を「Java EE」という文字列に置き換えることが可能です。
マクロはそれのプログラミング版のようなものです。
詳しい説明は次の項目で扱いますが、まずは機能をながめてみましょう。
-module(macro_test).
-export([print_out/1]).
-define(ERLANG,"Programming language Erlang").
print_out(Word) ->
io:format("~p is ~p~n",[?ERLANG, Word]).
-define
以下と、?Erlang
の部分がマクロに関わる部分です。
このプログラムの実行結果は以下の通りになります。
> macro_test:print_out(good).
"Programming language Erlang" is good
ok
?ERLANG が "Programming language Erlang" となっています。
マクロの定義は以下のように行ないます。
-define(Const, Replacement).
-define(Func(Var1,...,VarN), Replacement).
マクロはモジュール属性として宣言します。
第一引数が「置き換えられる式」で、第二引数が「置き換える式」となっています。
定数だけでなく、関数を置き換えることも可能です。
これは慣例に関することなのですが、置き換えられる式は全て大文字とすることが多いようです。
他の人が読みやすいように、マクロは全て大文字にするのが良いでしょう。
また、文字列の区切りは _ で繋ぎます。例えば、
LANG_ERLANG
といった具合です。
ErlangのマクロはC言語のマクロである
#define CONST Replacement
とほぼ同義です。
マクロを利用するには、以下のように記述するだけで大丈夫です。
?Const
?Func(Var1,...,VarN)
?の後に置き換えられる式を記述しています。
コンパイルの際にマクロを展開することにより、?Const
はReplacement
に置き換えられます。
マクロがどのように展開されるか実際に見てみます。
-define(TIMEOUT, 200).
...
call(Request) ->
server:call(refserver, Request, ?TIMEOUT).
上の記述は次のように展開されます。
call(Request) ->
server:call(refserver, Request, 200).
関数形式のマクロの置換えもやってみましょう。
-difine(MACRO1(X,Y), {a, X, b, Y}).
...
bar(X) ->
?MACRO1(a, b),
?MACRO1(X, 123)
これは次のように展開されます。
bar(X) ->
{a, a, b, b},
{a, X, b, 123}
関数をマクロとすると関数へのジャンプ命令がなくなる(アセンブリレベル)ため、若干プログラムの実行速度が早くなります。 ただし、通常のプログラムにおいてはほとんど差は生まれないので、命令ステップを削る場合は再帰関数内に絞るのが良いと思われます。
Erlangが定義しているマクロがいくつか存在し、それらはユーザが定義しないでも利用可能です。
定義済みマクロをユーザが再定義するのは誤用を避けるためにも、使用しないことが望ましいです。
定義済みマクロには以下のようなものが存在します。
?MODULE
モジュールの名前
?MODULE_STRING
モジュールの名前の文字列
?FILE
ファイル名
?LINE
現在何行目か
?MACHINE
実行システム
利用サンプル
-module(macro_test2).
-export([test/0]).
-define(OUT(X,Y),io:format("~s: ~p~n",[X,Y])).
test() ->
?OUT(module,?MODULE),
?OUT(module_string,?MODULE_STRING),
?OUT(file,?FILE),
?OUT(line,?LINE),
?OUT(machine,?MACHINE).
実行結果
> macro_test2:test().
module: macro_test2
module_string: "macro_test2"
file: "./macro_test2.erl"
line: 9
machine: 'BEAM'
ok
この中でよく利用されるのは MODULE と LINE の2つです。
MODUELは自分のモジュール名を関数内に記述する際に利用することが望まれます。
なぜなら、モジュール名が変更されても関数を定義し直す必要がないからです。
LINEはデバッグの際に現在どこにいるかを表示する場合に便利です。
マクロには条件式が存在します。デバッグの際に利用すると便利です。
あまり複雑なものにしないことがポイントです。
-undef(Macro)
マクロの定義を忘れる
-ifdef(Macro)
Macroが定義されていれば続く文を評価する。
-ifndef(Macro)
Macroが定義されていなければ続く文を評価する。
-else.
ifdef もしくは ifndef の後に使用される。
上記の条件がfalseの際に続く文を評価する。
-endif.
ifdef ifndef else の終わりを意味する。
利用サンプル
-module(m)
...
ifdef(debug).
-define(LOG(X), io:format("{~p,~p}: ~p~n",[?MODULE,?LINE,X]).
-else.
-define(LOG(X), true).
-endif.
debugが定義されていたら ifdef(debug) が true と評価されます。
マクロdebugをコンパイルする際に設定する方法があります。
$ erlc -Ddebug m.erl
-D の後に定義するマクロを記述しています。
また、Erlangシェルからのコンパイルの際には以下のようにします。
> c(m, {d, debug}).
{ok,m}
debugを指定することにより出力を詳細にし、指定しないことにより通常の実行が可能となります。