Julia’s macro expander solves these problems in the following way. First, variables within a macro result are classified as either local or global. A variable is considered local if it is assigned to (and not declared global), declared local, or used as a function argument name. Otherwise, it is considered global. Local variables are then renamed to be unique (using the gensymfunction, which generates new symbols), and global variables are resolved within the macro definition environment. Therefore both of the above concerns are handled; the macro’s locals will not conflict with any user variables, and time and println will refer to the Julia Base definitions.
Since those variables are not assigned to they are not considered local to the macro and do not have gensymed variables created for them. Thus they do not need to be escaped.
Edit: Leaving out the escape will cause behavior that is likely to be unexpected if the user feeds code into the macro that includes an assignment operation and user expects that assignment operation to apply to a variable outside of the macro.
Indeed, my code doesn’t use esc, but it does splat the y into a tuple and then splat the tuple into the call… and this works… I’m not sure why it works but just splatting y into the call doesn’t
@dlakelan, your code fails for me with exact same error (unknown r)
i.e. does this work for you (julia 1.5)?
import Printf.@sprintf;
import Dates.now;
macro myprintf(x,y...)
:(print(now(),":",@sprintf($x,($(y...),)...)))
end
function t()
r = 21
@myprintf("failing %.f\n", r * 2)
end
#calling function t is failing with unknown r
t()
_
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.5.0 (2020-08-01)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|__/ |
julia> import Printf.@sprintf;
julia> import Dates.now;
julia> macro myprintf(x,y...)
:(print(now(),":",@sprintf($x,($(y...),)...)))
end
@myprintf (macro with 1 method)
julia>
julia> function t()
r = 21
@myprintf("failing %.f\n", r * 2)
end
t (generic function with 1 method)
julia>
julia> #calling function t is failing with unknown r
julia> t()
ERROR: UndefVarError: r not defined
Stacktrace:
[1] macro expansion at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.5\Printf\src\Printf.jl:1230 [inlined]
[2] t() at .\REPL[4]:3
[3] top-level scope at REPL[6]:1
No, by default, all symbols in macros will be resolved in the scope where the macro is defined and all variables a macro assigns to get a randomized name as to not leak any definitions into the scope the macro is called in. If you actually want to reference variables in the scope the macro is called from or want to be able to define new variables in the macro call’s current scope, you need to wrap the symbol in esc. This leaves these symbols alone during macro expansion. You can also wrap whole expressions in esc, but you need to be careful, because you don’t want to accidentally leak variables, or reference functions from the caller’s scope, when you actually want to call a function defined in the scope the macro is defined in. This can lead to subtle bugs, which is why it’s not the default. You might want to read the section in the docs about macro hygiene to learn more.
Yes, I have read the macro hygiene section. I guess it didn’t help me much.
what I understand is that if you define a variable in your macro, it will have it’s name changed to not collide with existing variables in the macro eval context.
what I don’t understand is what esc does. it seems it should prevent the macro system from changing the names of variables. You’re broadcasting the esc across y. so it means you insert the contents of y without any macro hygiene changes?
It produces a special type of expression, namely, esc(:x) produces Expr(:escape, :x). Whenever macro expansion then encounters such an expression, it knows not to apply macro hygiene here. The exact expression type is probably an implementation detail, all you really need to know is that it’s some kind of marker specifically for macro expansion.