juliaのマクロについて
マクロとはメタプログラミングのことであり、プログラミングをプログラミングするみたいな感じである。
本記事はいくつかのマクロの例を実行してみることで、それらの理解を深めることが目的です。
なお、juliaのメタプログラミンのドキュメントに詳細が載っています。
@doc
@docは、何か値と紐づいたストリングを返すマクロである。
@docのコードを簡単な例で確認する。なお、SciPy.jlのsrcに出てくる@docに関するコードを参照しています。https://github.com/AtsushiSakai/SciPy.jl/blob/master/src/util.jl
@doc "hogehoge" g = sin(1) # g
実行
g #0.8414709848078965
@eval g #上と同じ
@doc g #hogehoge
ここで、@docをしたときに、裏でどのように実行されているかをみるには以下を実行する
@macroexpand @doc g
# :((Base.Docs.doc)((Base.Docs.Binding)(Main, :g)))
実際に、以下を実行すると@doc g
と同じ結果が返ってくることがわかる。
eval(:((Base.Docs.doc)((Base.Docs.Binding)(Main, :g))))
この例を通して、@docによって、g(ここではsin関数)に紐づいた"hogehoge"という文字列を返すことがわかった。
@show
これは、よく使う人も多いかもしれないが、printlnの強化版みたいなイメージである
先ほどの続きで確かめる。
@macroexpand g # :g
eval(:g) # 0.8414709848078965
@show g
# g = 0.8414709848078965
# 0.8414709848078965
@macroexpand @show g
@macroexpandを使えば、@showがどのようなコードなのかみることができ、確かにprintlnとvalueを返していることがなんとなくわかる。
quote
Base.println("g = ", Base.repr(begin
#= show.jl:1047 =#
local var"#30#value" = g
end))
var"#30#value"
end
@eval
与えられた関数とその引数を与えると、評価してくれるマクロになる。
a = zeros(4)
for (i, f) in enumerate([:sin, :cos, :tan, :log])
@eval $a[$i] = $(f)(1)
end
@assert a == [sin(1), cos(1), tan(1), log(1)]
b = zeros(4)
for (i, f) in enumerate([:sin, :cos, :tan, :log])
@eval $b[$i] = $(f)($i)
end
@assert b == [sin(1), cos(2), tan(3), log(4)] #OK
@assert b == [sin(1), cos(2), tan(3), log(100)] "NOOOOOO!" #これはエラー
おまけ:Exprとeval()
Meta.parse("1+1") # :(1 + 1)
Meta.parse("1+1") |> typeof # Expr
ex = :(1+1)
ex |> dump # データ型の構造を取得
結果
Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol +
2: Int64 1
3: Int64 1
ex.args, ex.headを実行すると、それぞれの値をとってこれる。
ここでExprの働きを詳しくみる。
Expr(:call, :+, 1,2,3) #:(1 + 2 + 3)
Expr(:call, :+, 1,2,3) == :(1+2+3) #と同じ
Expr(:call, :+, 1,2,3) == :(1+2+3) == Meta.parse("1+2+3") #と同じ
最後にexのargsの値を変更することも可能である
ex.args[end] = -1
ex # :(1 + -1)
eval(ex) #0
Expr は関数 eval() で評価することができる
まとめ
juliaのいろいろなマクロの例を見て理解を深めることができた。