Problem with local scope

I am faced with an issue that I can’t seem to figure out. Here is the source code (sorry I couldn’t come up with a more simple example):

macro handfunc(expr, kwargs...)
    expr = unblock(expr) # cleans expression
	expr = rmlines(expr) # cleans expression
    var = esc(expr.args[1]) # pulls variable name
    eq = esc(expr.args[2]) # pulls expression
    func_args = QuoteNode(expr.args[2]) #pulls function arguments to pass to function below
    found_func = InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :code_expr, (expr.args[2],)) # returns expression
    return quote
        x = handfunc($found_func, $func_args) # handfunc returns expression
        latex = $Main.eval(x) # need to evaluate it (but want to do it in calling scope)
        $var = $eq # evaluates expression
        latex # returns latex rendering
    end

end

Here is a snip of what it does (it currently works in main scope):

image

It basically parses through the called function body and returns the latex rendering of it, while also evaluating the Ix = Handcalcs.calc_Ix(b, h) portion as well.

Here is my issue. I would like to return just the function and then the macro would automatically evaluate the returned expression, but I can’t since found_func is an expression that, if inside the function, wouldn’t be evaluated.

Basically, it seems like I am left with trying to use the eval function, but that is for global scope and I am not sure how to get it to evaluate in calling scope.

  1. Is there a way to build an expression that would evaluate both?
  2. Or is there a way to get eval to work for calling scope?

Here is the source code: Handcalcs.jl-handfunc_branch. Macro starts at line 183.

Any help is much appreciated.

Don’t use eval — especially not in macros. Their job is to generate code, and it’ll get evaluated soon enough.

I think you want to call handfunc outside the quote block and then interpolate it in twice, once normally (which will be code that executes) and once into a quote node (to get the expression itself).

3 Likes
macro handfunc(expr, kwargs...)
    expr = unblock(expr) # cleans expression
	expr = rmlines(expr) # cleans expression
    var = esc(expr.args[1]) # pulls variable name
    eq = esc(expr.args[2]) # pulls expression
    func_args = QuoteNode(expr.args[2]) #pulls function arguments to pass to function below
    found_func = InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :code_expr, (expr.args[2],)) # returns expression
    ret_expr = handfunc(found_func, func_args)
    return quote
        latex = $ret_expr
        $var = $eq # evaluates expression
        latex # returns latex rendering
    end

end

I just tried one interpolation first. This results in an error though because the found_func is an expression that needs to be evaluated first.
Before:

found_func = code_expr($(Expr(:escape, :calc_Ix)), (Base.typesof)($(Expr(:escape, 5)), $(Expr(:escape, 15))))

After:

found_func = :(function calc_Ix(b, h)
    #= C:\Users\CMILLER\.julia\dev\Handcalcs\src\Handcalcs.jl:158 =#
    #= C:\Users\CMILLER\.julia\dev\Handcalcs\src\Handcalcs.jl:159 =#
    Ix = (b * h ^ 3) / 12
    #= C:\Users\CMILLER\.julia\dev\Handcalcs\src\Handcalcs.jl:160 =#
    return Ix
end)

Could you give an example?

Thanks.

Maybe this section of an older post of mine might help jkrumbiegel.com - Julia macros for beginners

I don’t understand the code you have there so I can’t really comment on it in detail. But when you get errors due to expressions like $(Expr(:escape, :calc_Ix)) it usually means that you are escaping some piece of code twice.

Escaping has to be done at exactly the right granularity, ask yourself which of your code should appear in place of the macro as if the user had written it (often this is a verbatim or almost verbatim copy of the actual code written by the user) and which of the code is helper code that relates only to your module and shouldn’t in any way interact with the other top-level user code. That’s the stuff you do not want to escape.

So when you have a double escaping error, your logic was flawed because marked a piece of code as user code, interpolated it into some other code, but then also marked that code as user code. In this case, the inner interpolated code should not have been escaped first because it was going to be anyway, as part of the escaped outer code. (Admittedly, this is sometimes an annoying complexity when writing macros, because you have to know for each expression fragment you generate what the final context it ends up in has to be, and if you’re allowed to escape or not.)

2 Likes

Yeah I don’t know… That code you are referring to is generated by a function in the InteractiveUtils library which is code I did not write. Even the function parameters I pass to it is copied from CodeTacking.jl. That code works great when I interpolate it into the quote.

The problem is (at least I think) I need to pass the interpolated code from that InteractiveUtils function to the function I wrote (handfunc). But my function also returns an expression, but since it is within a quote, it doesn’t evaluate when macro is called… That is why I am using the eval function to force it to evaluate, but eval is global scope only…

I guess a general question: Can you within a macro, use a function that generates an expression. This expression then needs to be evaluated somehow. Let’s call the evaluated expression x. Then use that x within another function that generates an expression that then needs to be evaluated?

At a fundamental level, macros should just generate the code to do what you want — they aren’t the ones to do it.

So yes, your macro can output code that defines a function and then calls it.

But I think you’re getting tied in knots because you’re currently outputting code that happens to evaluate to an expression. But that might be backwards — you probably want to just construct the expression at macro-time (outside the quote). Then you can reference the expression in two ways: once that defines the function and once quoted-ly.

To simplify this, here’s a macro only deals with functions of two arguments named x and y:

julia> macro xy(expr)
           quote
               fun = ($(esc(:x)),$(esc(:y)))->$(esc(expr))
               ret = fun($(esc(:x)), $(esc(:y)))
               Expr(:(=), $(QuoteNode(expr)), ret)
           end
       end
@xy (macro with 1 method)

julia> let x=1, y=2
           @xy x+y^2
       end
:(x + y ^ 2 = 5)

julia> let x=3, y=2
           @xy x+y^2
       end
:(x + y ^ 2 = 7)