Macro: escaping, evaluation, scope etc,

Playing around with macros.

macro gk(x)
    ks = keys(eval(x))
    return ks
end

p = (a=1, b="b")

julia> @gk p
(:a, :b)

That’s OK, that’s what I want. Now, if the macro is defined inside a separate module, I couldn’t find any way to achieve te same result. No escape :frowning_face:

It’s almost never necessary or helpful to use eval in a macro. A macro takes code (not the values represented by that code) and produces new code, so it shouldn’t need to eval anything.

It seems like you don’t need a macro at all–using a function solves the problem perfectly:

function gk(x)
  keys(x)
end

(or, at that point, don’t even define gk at all and just call keys yourself).

Can you explain more about the problem you actually want to solve?

2 Likes
macro unp(x)

    local nt = @eval $x
    for k in keys(nt)
        local v = nt[k]
        @eval $k = $v
    end
end
p = (a=1, b="b")

julia> @unp p

julia> a
1

julia> b
"b"

Unpack a NamedTuple and create new variables in the calling scope. I know, that’s unsafe, and it is not that I actually need it, but I’m curious how can that be done. In the example above it works. How can that be done if the macro called in a different scope?

No, that’s not possible in general. The way Julia works, all (local) variables need to be resolved statically.

3 Likes

Yes, indeed, I don’t need a macro here, just function:

function unp(x)
    exs = [Expr(:(=), k, QuoteNode(x[k])) for k in keys(x)]
    eval.(exs)
end

p = (a=1, b="b")

julia> a
ERROR: UndefVarError: a not defined
 
julia> unp(p);

julia> a
1

julia> b
"b"
1 Like

No, we mean that eval always evaluate on global scope, and therefore your function will always create global variables. If you call it inside a function it will not create variables local to the function, and if the function had any local variables of the same name they will be shadowing such global variables. What you see only work because you are working in global scope in the REPL.

julia> function unp(x)
           exs = [Expr(:(=), k, QuoteNode(x[k])) for k in keys(x)]
           eval.(exs)
       end
unp (generic function with 1 method)

julia> function test_unp(p)
           a = 10
           b = "c"
           unp(p)
           @show a
           @show b
           return
       end
test_unp (generic function with 1 method)

julia> test_unp((a=1, b="b"))
a = 10
b = "c"

julia> a
1

julia> b
"b"

Here you can see that the variables are global (appeared out of the function that called unp) and that the local variables shadowed them inside the function.

If we do not define a local a and a local b inside the test_unp function, then the function see the global variables, but I am not sure this works outside of the REPL. Julia has something called “Age of the world” that sometimes prevents things just evaluated to be used immediately in the sequence.

1 Like

OP everyone in this thread is correct, you should not be using eval ever. It is not though it works with expressions, in Julia using “metaprogramming” refers to re-writing expressions in local scope.

You may be interested in the following packages

  • StaticModules.jl which provides the macro @with for working with objects.
  • UnPack.jl which allows easy unpacking of objects
  • DataFramesMeta.jl for doing a similar unpacking as StaticModules.jl but with extra performance benefits designed for DataFrames
  • AddToField.jl which allows for packing objects into dictionaries or named tuples easily.
3 Likes