Esc() in macro definitions in meta programming


#1

Hi, I am not sure if this is the right place for meta programming questions. Please redirect me if you think there is a better place to ask this question.

I am trying to understand the use of $(esc()) in macro definitions. I read the following source to learn about it.
Source1,
Source2.

My understanding is that in order to keep macro hygiene, I need to use esc to keep global space variables seperate from local variables.
So I wrote the following macro, and expanded it to see the result.

Julia> n = 1
Julia> 
macro esc_example(a)
    quote 
        n = 3
        println(n)
        println($n)
        println($(esc(n)))

        println($a)
        println($(esc(a)))
    end
end

Julia> @esc_example(n)
3
1
1
3
1

Julia> macroexpand(:(@esc_example(n)))
quote  # REPL[48], line 3:
    #33#n = 3 # REPL[48], line 4:
    (Main.println)(#33#n) # REPL[48], line 5:
    (Main.println)(1) # REPL[48], line 6:
    (Main.println)(1) # REPL[48], line 8:

    (Main.println)(#43#n) # REPL[56], line 9:
    (Main.println)(n)
end

I defined n as 1 in REPL, and a local variable n as 3.
I am not sure if esc() can be used on macro arguments, so I want to test it as well.

Let me go through it line by line, left side is the macro definition, and right side is the macro expansion .

  1. n = 3 => #33#n = 3 # REPL[48], line 4:
    n is defined locally as 3, so when expanding macro, the name is reassigned with a name #33#n to make sure it does not collide with names in global space. I think this one is straight forward.

  2. println(n) => (Main.println)(#33#n) # REPL[48], line 5:
    Just prints out the local n value, understood.

  3. println($n) => (Main.println)(1) # REPL[48], line 6:
    $n is string interpolation. Replacing n in global space with its value 1.
    This one is a little confusing because I thought I have to use $(esc(n)) to get the value of n in global space.

  4. println($(esc(n))) => (Main.println)(1) # REPL[48], line 8:
    This is the use of esc(). and it is getting the value of n from global space. Understood. But I thought the macro expansion will replace it with something like Main.n, rather than the value 1, because it should not be evaluated at macro expand time. Am I right? And if they have the same effect, what is the benefit of using esc()?

  5. println($a) => (Main.println)(#43#n) # REPL[56], line 9:
    I think this line is a bug. Because this line should prints out the value of a, which is n in global space, the value 1. But instead it is replaced with the local n.

  6. println($(esc(a))) => (Main.println)(n)
    Like I said in the before, I don’t know if esc() can be used on arguments so I am trying it out.
    I was expecting an error, but it was replaced with n in global space. Maybe someone can help me understand this part.

Here is the version info from Julia

julia> versioninfo()
Julia Version 0.6.3
Commit d55cadc350 (2018-05-28 20:20 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin14.5.0)
  CPU: Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, skylake)

I apologize for the long post. I am trying my best to explain my confusion.
And thank you for reading through my question.


#2

You’ve mixed up interpolating and escaping a bit in your example. When you do $n, you are splicing the current value of n (in this case, the value in the global scope) into the resulting expression. Escaping this value does nothing. You’ve essentially hard-coded that 1 into the resulting expression.

esc() is applied to an expression or a symbol to cause that symbol to be resolved in the caller’s scope, not the macro definition’s scope. For example:

julia> macro set_n()
         quote
           $(esc(:n)) = 1
         end
       end
@set_n (macro with 1 method)

julia> @macroexpand @set_n()
quote  # REPL[13], line 3:
    n = 1
end

julia> n
ERROR: UndefVarError: n not defined

julia> @set_n()
1

julia> n
1


#3

Ah, I see. That makes sense now. I missed the part that esc() takes an expression. Thank you very much.

But do you think bullet 5 ( the line starts with println($a) ) is a bug? Because I think when using $a, it should be expanded to n in global space, but in this case, it was expanded to local n.