How is macro arguments resolved?

question
macros
metaprogramming

#1

In the official documentary, a macro example is provided:

macro time(ex)
    return quote
        local t0 = time()
        local val = $ex
        local t1 = time()
        println("elapsed time: ", t1-t0, " seconds")
        val
    end
end

After this macro, a problem is immediately brought up:

module MyModule
import Base.@time

time() = ... # compute something

@time time()
end

In the above snippet, ex in the macro definition will be resolved to the standard time function, not the one defined in the snippet.

This behavior seems to be quite strange to me. Naively, I would think since ex is obtained from macro calling environment, how come it is resolved in macro defining environment? How is macro arguments resolve exactly?


#2

Please finish reading that page of doc.


#3

I finished the page before I asked. I know that the way out is to use esc, but I still don’t understand why ex should be resolved in macro defining environment. If I do

module MyModule
import Base.@time

myfun() = ... # compute something

@time myfun()
end

then myfun is not even defined in macro defining environment.


#4

Right? What’s the problem? The base one should have the correct behavior and resolve to your function. The first example in the hygiene section is just demonstrating the problem and it is corrected a few lines later.


#5

Does this mean macro arguments should always be escaped?


#6

Not always but yes when you want them to be treated as normal code.


#7
  1. Can you give me an example when escape is not needed in defining macros? I cannot come up with even one example where macro arguments should not be resolved in macro calling environment.

  2. Here comes another question: If macro arguments should almost always be escaped, why doesn’t the parser use this as the default behavior?


#8

Anything that deconstruct the expression passed in. Or does not treat the input as normal variables. You may also want to resolve some symbols in the macro’s environment. The cartisian loop macros, @static, @compat, @pyimport etc are all doing that to some degrees. In general, macros are way more powerful than simply wrapping some code in some enviroment. The macro can use all syntactic information in the function to do all what it want, which for any non-trivial transformation will not want the arguments to be escaped directly.

No macro arguments do not almost always need to be escaped. In many cases they’ll be escaped as pieces. Escaping pieces by default could be a better way but it’s not possible without huge breakage since it’s not at all under the runtime’s control currently, let alone the parser. Ref https://github.com/JuliaLang/julia/pull/10940


#9

Thank you for your explanation. @yuyichao


#10

It’s also possible to just escape everything and use gensym to avoid name collisions.


#11

And interpolate in any local variables and functions