Evaluate Created Expression Within Function

Hello all.

I am writing some code that adds syntax sugar to subsetting dataframes.

I want to pass :(:SepalLength .> (:SepalWidth .* 2)) and df into a function and it create a BitVector by evaluating df.SepalLength .> (df.SepalWidth .* 2).

I created a function that turns

:(:SepalLength .> (:SepalWidth .* 2))

into

:(df.SepalLength .> (df.SepalWidth .* 2)).

However, when I try evaluating the new expression with eval() I run into an error because it evaluates it at the global scope and df is not defined.

At this time the only way I know how to overcome this issue is by evaluating an expression within a local scope. Is this possible? If not, is there another way to approach this problem to get similar results?

Here is the function. I’m sure there are many issues with it, but the part I am mainly concerned about is evaluating the newly created expression to return a BitVector. Other feedback is welcomed too. Thank you all for your time and aid.

NOTE: I know DataFramesMeta exists. This is for something different.

function subsettingExpressionToBitVector(df::DataFrame, ex::Expr)
    newCollec = []
    for expression in ex.args
    if typeof(expression) <: Expr
        expression = subsettingExpressionToBitVector(df, expression)
    end

if typeof(expression) <: QuoteNode
    if eval(expression) ∈ propertynames(df)
    column = propertynames(df)[eval(expression) .== propertynames(df)][1]
    expression = Expr(:., Symbol("df"), QuoteNode(column))
    end
end
    push!(newCollec, expression)
end
newExpr = Expr(:call, newCollec...)

    return eval(newExpr) # Using eval() is the issue.
end

eval only works in global scope, however, what you want do not seem to be a function but instead a macro, which is basically a function that takes whatever arguments as an expression, at compile time, and return an expression to substitute itself before the code is compiled. Did you look at macros in Julia Manual?

1 Like

Thank you for the reply. Yes, I have looked at macros a couple of times in the manual. I must admit I was avoiding them because I don’t understand them well. (I will look into them now.) Is there no way to execute an eval() command on the local scope? (Or something similar.) I haven’t been able to find anything about it.

Thank you again for your help.

No, it is by design that eval is limited to global scope, so that everything in the local scope is monitored by the JIT compiler, which is a prerequisite for optimizations.

As for your question, a macro is exactly what you need:

replace_symbol(ex, obj) = ex
replace_symbol(sym::QuoteNode, obj) = :($obj.$(sym.value))
replace_symbol(ex::Expr, obj) = Expr(ex.head, replace_symbol.(ex.args, obj)...)
macro replace_sym(sym, ex)
	# escape the whole expression so that they represent variables in the caller's scope instead of local to the macro
	esc(replace_symbol(ex, sym))
end
let
	df = DataFrame(a=1:4, b=3:6)
	@replace_sym df :a .> :b .* 2
end