5

My problem with this code is that the macro fails when an interpolated string is passed:

macro t(a, b, c)
    quote
        println(:c, " = " , $c)
        ($a, $b, $c)
    end
end

function test()
    # works:
    @t 1 2 3

    # doesn't work:
    x = π
    @t 1 2 "x is $x"
end

test()
test()
c = 3   # works
ERROR: UndefVarError: x not defined

So inside t the interpolated x value is unavailable. Is there a solution to this, or is using a macro just a bad idea here?

2 Answers 2

5

In order to achieve what you want you need to use esc:

macro t(a, b, c)
    quote
        local a1 = $(esc(a))
        local b1 = $(esc(b))
        local c1 = $(esc(c))
        println(:c, " = ", c1)
        (a1, b1, c1)
    end
end

Note that I define variables a1, b1, and c1 once and then reuse them. The reason is that if you have written something like:

macro t(a, b, c)
    quote
        println(:c, " = ", $(esc(c)))
        ($(esc(a)), $(esc(b)), $(esc(c)))
    end
end

which is natural to do (or maybe not :)), you would get problems as c would be evaluated twice, as eg. in this example:

julia> macro t(a, b, c)
           quote
               println(:c, " = ", $(esc(c)))
               ($(esc(a)), $(esc(b)), $(esc(c)))
           end
       end
@t (macro with 1 method)

julia> function test()
           @t 1 2 rand()
       end
test (generic function with 1 method)

julia> test()
c = 0.03771143425073453
(1, 2, 0.1819496773810383)

Note that a different value is printed and different value is returned. This problem was present in your original macro (even if it used global variables as @Bill noted):

julia> macro t(a, b, c)
           quote
               println(:c, " = " , $c)
               ($a, $b, $c)
           end
       end
@t (macro with 1 method)

julia> @t 1 2 rand()
c = 0.7021554643798531
(1, 2, 0.6363717837673994)

In general I think that @code_lowered and @macroexpand macros will be useful for you when you debug code using metaprogramming.

Sign up to request clarification or add additional context in comments.

Comments

1

Such macros are evaluated in such a way that any variable names not local to the macro function itself are considered global. The calling scope at the point of macro is not used.

So this works:

macro t(a, b, c)
    quote
        println(:c, " = " , $c)
        ($a, $b, $c)
    end
end

function test()
    # works:
    @t 1 2 3

    x = π  # this x is not seen inside the macro's scope
    @t 1 2 "x is $x"
end

x = 2π  # this one is global

test()

1 Comment

One can use hygene to fix the problem you mention. I have added an example in my answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.