#julia #metaprogramming
#джулия #метапрограммирование
Вопрос:
Я пытаюсь создать макрос, который заменяет тело функции таким образом, чтобы:
@foo function bar(a,b,c)
*my code here*
end
и я продвинулся довольно далеко, но мой код здесь должен знать параметры bar(...)
, которые можно выполнить
с помощью Base.@locals
. Но проблема в том, что @foo
ее нужно будет вставить, и я думаю, что она использует неправильную «локальную область» или вычисляется до появления параметров.
foo выглядит примерно так: ( expr.args[2]
является телом функции)
macro foo(expr::Expr)
...
expr.args[2] = quote
locals = Base.@locals
println(locals)
...
end
:($expr)
end
Но при вызове этой созданной позже функции выводится только пустой dict, тогда как созданная вручную функция выводит параметры при использовании того же кода:
function test(a,b,c)
println(Base.@locals)
end
test(1,2,3) # returns: Dict{Symbol,Any}(:a => 1,:b => 2,:c => 3)
Итак, мой вопрос в том, как мне обойти это, чтобы Base.@locals
(или аналогичный метод) дал мне параметры?
Ответ №1:
Вместо того, чтобы полагаться на Base.@locals
, я бы получал аргументы функции непосредственно из определения и делал что-то вроде этого:
using MacroTools
macro foo(expr)
def = MacroTools.splitdef(expr)
make_pair(arg) = :($(Meta.quot(arg)) => $arg)
def[:body] = quote
d = Dict($(map(make_pair, def[:args])...))
println(d)
$(def[:body])
end
MacroTools.combinedef(def)
end
Несколько замечаний и пояснений к этому коду:
- Макросы, пытающиеся переписать / изменить определения функций, могут значительно выиграть от использования функций
splitdef
иcombinedef
MacroTools
. Глобально предлагаемый макрос состоит из 3 этапов:- разделите определение функции на отдельные части;
- вставьте нужный код в начало тела функции;
- повторно объедините все части (включая измененное тело) в законное определение функции.
- Словарь, содержащий аргументы функции, состоит из следующих ключевых компонентов:
make_pair
принимает заданное имя переменной (скажем, в виде символа:a
) и возвращает выражение, вычисляющее пару:a => a
;- затем эта
make_pair
функцияmap
передается всем аргументам, исходящим из (разложенного) определения функции, для построения массива пар аргументов; - наконец, этот массив помещается в
Dict
конструктор.
Мы можем проверить, что этот пример определения функции расширяется до ожидаемого кода:
julia> Base.remove_linenums!(@macroexpand @foo function bar(a,b,c)
a b c
end)
:(function Main.bar(var"#26#a", var"#27#b", var"#28#c"; )
var"#25#d" = Main.Dict(:a => var"#26#a", :b => var"#27#b", :c => var"#28#c")
Main.println(var"#25#d")
begin
var"#26#a" var"#27#b" var"#28#c"
end
end)
… и в более общем плане все работает так, как ожидалось, и во время выполнения:
julia> @foo function bar(a, b, c)
a b b
end
bar (generic function with 1 method)
julia> bar(1, 2, 3)
Dict(:a => 1,:b => 2,:c => 3)
5