[SOLVED] How can I evaluate in Main from a module?

Hello!
I am trying to make a macro, @each, which will take each value and compare the falsehood of it to a certain condition. The problem is, while it works fine so long as the macro is globally defined, i.e. in a notebook:

macro each(exp::Expr)
    x = exp.args[2]
    if length(exp.args) == 2
        for value in eval(x)
            state = eval(Meta.parse(string(exp.args[1], "(", value, ")")))
            if state != true
                return(false)
            end
        end
    end
    for value in eval(x)
        state = eval(Meta.parse(string(value," ", exp.args[1], " ", exp.args[3])))
        if state != true
            return(false)
        end
    end
    return(true)
end

I am not so sure that the function version is entirely working (yet,) though it might be. Here is that macro now working:

if @each [5, 10, 15] >= 5
    print("hi")
end

hi

if @each [5, 10, 15] > 50
    println("This will not print")
end

x = [5, 5]

# Yes I know, we have .==, but we don't have
#    .ismissing(), and things like that.
if @each x == 5
    println("Yes.")
end

Yes.

The only issue comes when I want to evaluate something like the last expression after including a module to do so.

include("src/Each.jl")

if Main.Each.@each x < 20
    
end

LoadError: UndefVarError: x not defined
in expression starting at In[4]:1

Of course, providing values still works:

if Main.Each.@each [5, 10, 15] < 20
    println("yeah")
end

I noticed that macro definitions by default receive a module, i.e. main under the alias module, pretend that has two __s on either side, as the formatting is… Actually:

__module__

Thank goodness for method errors for telling me that, by the way. Anyway, I was just wondering if there is some sort of way I can evaluate this module into here. As you can see, evaluating a string Main.x yield x:

eval(Meta.parse("Main.x"))
3-element Vector{Int64}:
  5
 10
 15

But for some reason, evaluating Main.x does not really do anything… Additionally, it would be nice to keep them as symbols.

An update…
I thought maybe about using getfield() for this, it seemed apt as I could get the field :x from module, once again pretend that module has underlines around it. That ended up working perfectly. Somehow after pulling my hair out for an hour, and FINALLY deciding to post on the forums, I ended up figuring it out in like a minute after posting. Really, I blame myself for not thinking of getfield() earlier. Here is the new code:

macro each(exp::Expr)
    x = exp.args[2]
    xname = ""
    if contains(string(x), '[')
        xname = eval(x)
    else
        xname = getfield(__module__, Symbol(x))
    end
    if length(exp.args) == 2
        for value in xname
            state = eval(Meta.parse(string(exp.args[1], "(", value, ")")))
            if state != true
                return(false)
            end
        end
    end

    for value in xname
        state = eval(Meta.parse(string(value," ", exp.args[1], " ", exp.args[3])))
        if state != true
            return(false)
        end
    end
    return(true)
end

So if anyone is wondering how to get values, and work with values from main from within a module, from expressions, this is at least one working way to do so.

The issue here is your macro is performing all of the calculations and then returning true or false. What you want the macro to return is an expression to evaluate in the main scope. Your macro should probably contain something along the lines of

return esc(:( something ))

or

out = quote
    long(expression)
    with_more.lines()
end
return esc(out)

The best tip I’ve gotten for this was: put almost nothing in your macro itself:

macro each(ex)
    return esc(_each_helper(ex))
end

function _each_helper(ex)
    # turn expression ex into another expression which, 
    # when evaluated in Main, does the right thing
end

You should never have to call eval explicitly.

Oh, and for debugging macros, @macroexpand @each [1, 2, 3] > 0.5 is your friend.

One more thing. If this is actually functionality you need and not just an exercise in metaprogramming, can I suggest all(x .>= 5) or the more performant but less self-explanatory all(>=(5), x)?

3 Likes

Diiiiid not realize this method existed.

It’s great. Especially the all(f::Function, x) version, which short-circuits if f evaluates to false at any point.

julia> all(x->(println(x); x<3), 1:10)
1
2
3
false

(There’s also any for the opposite function)

1 Like

Looks very useful, thank you for sharing this with me!