Since temp_str is not escaped you do not need the let. Local variables get renamed by default, and since temp_str is assigned to and not explicitly marked as global it is a local variable.
@chakravala, thanks! In fact, this is exactly how I implemented it now in production code, for all the reasons you mentioned. Initially, I put a variable just to be more explicit where the problem is. Now it is of course time to clean up.
Actually looks like I still run into a problem as above solution is not working in some very common scenario. Namely passing a variable into the macro (passing a number directly works)
import Printf.@sprintf;
import Dates.now;
macro myprintf(x, y...)
:(print(now(),":",@sprintf($x, $(esc(y))...)))
end
@myprintf("working %.f", 0.42)
#below fails with ERROR: MethodError: no method matching isfinite(::Symbol)
result = 0.43
@myprintf("failing %.f", result)
Thanks @simeonschaub! that indeed works as expected.
I wish somebody with a deep knowledge of macros would have written a bit deeper on the topic with tips and tricks, going into tricky situations like those
Yet, one more problem in this solution. using this macro inside a function seems to be not working
import Printf.@sprintf;
import Dates.now;
macro myprintf(x, y...)
:(print(now(), ":", @sprintf($x, $(esc.(y)...))))
end
function t()
r = 21
@myprintf("failing %.f\n", r * 2)
end
#calling function t is failing with unknown r
t()
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?