Macro: escaping, interpolation, scope etc

I would like to achieve something along the following lines: when I write @all(A), it should replace it with A[ix,iy,iz] in the caller’s scope (ix, iy and iz are defined in the caller’s scope).

So, I wrote the following macro:

macro all(A) :( esc($A[ix,iy,iz]) ) end

However, it does not work as wished:

$>julia

julia> macro all(A) :( esc($A[ix  ,iy  ,iz  ]) ) end

@all (macro with 1 method)

julia> B = ones(2, 3, 1)
2×3×1 Array{Float64,3}:
[:, :, 1] =
 1.0  1.0  1.0
 1.0  1.0  1.0

julia> ix = 2;

julia> iy = 1;

julia> iz = 1;

julia> @macroexpand @all(B)
:((Main.esc)((Main.B)[Main.ix, Main.iy, Main.iz]))

julia> @all(B)
:($(Expr(:escape, 1.0)))

julia> eval(@all(B))
ERROR: syntax: invalid syntax (escape #<julia: 1>)
Stacktrace:
 [1] eval at ./boot.jl:319 [inlined]
 [2] eval(::Expr) at ./client.jl:393
 [3] top-level scope at none:0

And if I try to call the macro from within a function, I get:

julia> function f()
           C = ones(5,5,5);
           ix = 1;
           iy = 2;
           iz = 1;
           println(@macroexpand @all(C))
           println(@all(C))
       end
f (generic function with 1 method)

julia> f()
(Main.esc)((Main.C)[Main.ix, Main.iy, Main.iz])
ERROR: UndefVarError: C not defined
Stacktrace:
 [1] f() at ./none:7
 [2] top-level scope at none:0

Questions

  1. When called within the function f(), why does the macro expand to an expression containing Main.C and not f.C (and Main.ix and not f.ix etc)?
  2. How can I fix this macro to achieve what I described at the beginning?

Thanks!

(1) Macros are evaluated before the functions that contain them. You see Main._ because that is the context in which the macro is evaluated.

(2) I’d start with macro all(A) :( $(esc(A))[ix,iy,iz] ) end.
or macro all(A, ix, iy, iz)

@thanks @JeffreySarnoff! The modification that you suggested makes the macro work if called from Main:

$>julia

julia> macro all(A) :( $(esc(A))[ix,iy,iz] ) end
@all (macro with 1 method)

julia> B = ones(2, 3, 1)
2×3×1 Array{Float64,3}:
[:, :, 1] =
 1.0  1.0  1.0
 1.0  1.0  1.0

julia> ix = 2;

julia> iy = 1;

julia> iz = 1;

julia> @all(B)
1.0

julia> @macroexpand @all(B)
:(B[Main.ix, Main.iy, Main.iz])

What do you suggest to make it work when called from a function. The current macro leads to the following error:

$>julia

julia> macro all(A) :( $(esc(A))[ix,iy,iz] ) end
@all (macro with 1 method)

julia> B = ones(2, 3, 1)
2×3×1 Array{Float64,3}:
[:, :, 1] =
 1.0  1.0  1.0
 1.0  1.0  1.0

julia> function f()
           C = ones(5,5,5);
           ix = 1;
           iy = 2;
           iz = 1;
           println(@macroexpand @all(C))
           println(@all(C))
       end
f (generic function with 1 method)

julia> f()
C[Main.ix, Main.iy, Main.iz]
ERROR: UndefVarError: ix not defined
Stacktrace:
 [1] f() at ./none:15
 [2] top-level scope at none:0

And escaping ix,iy and iz does not solve it:

julia> macro all(A) :( $(esc(A))[esc(ix),esc(iy),esc(iz)] ) end
@all (macro with 1 method)

julia> B = ones(2, 3, 1)
2×3×1 Array{Float64,3}:
[:, :, 1] =
 1.0  1.0  1.0
 1.0  1.0  1.0

julia> function f()
           C = ones(5,5,5);
           ix = 1;
           iy = 2;
           iz = 1;
           println(@macroexpand @all(C))
           println(@all(C))
       end
f (generic function with 1 method)

julia> f()
C[(Main.esc)(Main.ix), (Main.esc)(Main.iy), (Main.esc)(Main.iz)]
ERROR: UndefVarError: ix not defined
Stacktrace:
 [1] f() at ./none:15
 [2] top-level scope at none:0

Note: a macro all(A, ix, iy, iz) is not an option as one of the aims of the macro is not to have to specify ix, iy and iz.

Thanks!

julia> macro all(A) :( $(esc(A))[$(esc(:ix)), $(esc(:iy)), $(esc(:iz))] ) end
@all (macro with 1 method)

julia> B = ones(2, 3, 1)
2×3×1 Array{Float64,3}:
[:, :, 1] =
 1.0  1.0  1.0
 1.0  1.0  1.0

julia> function f()
           C = ones(5,5,5);
           ix = 1;
           iy = 2;
           iz = 1;
           println(@macroexpand @all(C))
           println(@all(C))
       end
f (generic function with 1 method)

julia> f()
C[ix, iy, iz]
1.0

Great, thanks @kristoffer.carlsson! It works. However, it has become really ugly to read. How can I separate the majority of the escape/scope/interpolation story from the essence of the macro (the expression A[ix,iy,iz])? The ideal case would be something like any of the following (which do not work of course):

macro _all(A,ix,iy,iz) :( A[ix,iy,iz] ) end
macro all(A) @_all($(esc(A)),$(esc(:ix)),$(esc(:iy)),$(esc(:iz))) end

or

macro all(A)
    A = $(esc(A));
    ix = $(esc(:ix));
    iy = $(esc(:iy));
    iz = $(esc(:iz));
    return :( A[ix,iy,iz] )
end
macro all(A)
    A = esc(A)
    ix = esc(:ix)
    iy = esc(:iy)
    iz = esc(:iz)
    return :( $A[$ix, $iy, $iz] )
end

or

macro all(A)
    A, ix, iy, iz = esc.((A, :ix, :iy, :iz))
    return :( $A[$ix, $iy, $iz] )
end
3 Likes

Thanks @kristoffer.carlsson! I love the second one! It is so concise it can even go on one line :slight_smile:

macro all(A) A,ix,iy,iz = esc.((A,:ix,:iy,:iz)); :($A[$ix,$iy,$iz]) end

@kristoffer.carlsson and @JeffreySarnoff, I just found a shorter solution:

macro all(A) esc(:($A[ix,iy,iz])) end

Here is its usage within the example from above:

$>julia

julia> macro all(A) esc(:($A[ix,iy,iz])) end
@all (macro with 1 method)

julia> function f()
           C = ones(5,5,5);
           ix = 1;
           iy = 2;
           iz = 1;
           println(@macroexpand @all(C))
           println(@all(C))
       end
f (generic function with 1 method)

julia> f()
C[ix, iy, iz]
1.0

So to summarize, we have the two following solutions in this topic:

Solution 1:

macro all(A) A,ix,iy,iz = esc.((A,:ix,:iy,:iz)); :($A[$ix,$iy,$iz]) end

Solution 2:

macro all(A) esc(:($A[ix,iy,iz])) end

Is there any reason to prefer one solution over the other (besides the conciseness which speaks clearly for solution 2)?
Could you explain what is the difference, i.e. what exactly happens when these macros are evaluated?
Thanks!