# Auto-evaluate function/expression in local scope

Hi guys,

I have a problem which I think should be fairly easy to solve (I guess) but I don’t know how.
I’ll try to describe it as on-point as possible.

I want to define an array of “formulas” that I want to evaluate at different times.
I tried it with expressions and it worked really well at first sight. Here is a simplified example:

``````ex1 = :(3 + a^2)
ex2 = :(5 + a)
ex3 = :(12)
arr = [ex1, ex2, ex3]

a=15
eval.(arr)
``````

This is exactly what I need (I love that I can evaluate the whole array with just one `.eval` call and get an array back) BUT: it only works with global `a` since expressions always get evaluated in global scope.
Now where I want to evaluate this stuff is in a scope where `a` is local.

Of course I googled and found the advice “try it with functions”. Apart from the problem that I don’t know how to call a bunch of functions in an array with one call I also wasn’t successful in trying to access local variables.

This simple example (with just one function) doesn’t work:

``````f = () -> a + b^2 + 20

for a = 1:10
b = a-1
println(f())
end
``````

I get an `UndefVarError: b not defined`.

How could I make this work?
Any help is greatly appreciated!

My spider senses give me the suspicion of a XY problem - Wikipedia but welcome to this fine community, I am sure you are not a villain

What I mean is, that just solving your direct questions seem to lead you on a bad path, where other problems will likely arose.

For your first question, what do you mean by local. If local is local to a module there is

``````Base.eval(m::Module, x)
``````

but this is not a recommendation to use it.

For your second question it would be best to just use function parameters, like e.g.:

``````julia> f = [ (a,b) -> a + b^2 + 20 , (a,b) -> a + b^2 + 10 ]
2-element Vector{Function}:
#25 (generic function with 1 method)
#26 (generic function with 1 method)

julia> for a = 1:10
b = a-1
println( [ x(a,b) for x in f ] )
end
[21, 11]
[23, 13]
[27, 17]
[33, 23]
[41, 31]
[51, 41]
[63, 53]
[77, 67]
[93, 83]
[111, 101]
``````

2 Likes

Instead of local variables, you could use a dictionary with symbols as keys that work as variable names. Then, you could make a macro that transforms formulas into functions that take such dictionaries:

``````using MacroTools

macro makefun(expr)
new_expr = MacroTools.postwalk(expr) do subex
if @capture(subex, :s_)
return :(dic[\$(QuoteNode(s))])
else
return subex
end
end
return :( dic -> \$new_expr )
end

f = @makefun :a + :b^2 + 20

variables = Dict()
for a = 1:10
variables[:a] = a
variables[:b] = a - 1
println(f(variables))
end
``````

Note: inspired in:

1 Like

Thank you both so much!

@oheil to answer your first question: I mean local as in the local scope of another function where `a` and `b` would be parameters of said function. Your answer to my second question is a nice solution though - evaluating the functions in a list comprehension is actually a working idea.

Beyond that I was just wondering if I could also find a solution to address future local variables that I already know the names of. This is (to some length) what @heliosdrm provided.

Of course I could provide more information of the real problem but I don’t think it’s needed here. This was more of a “what would be an elegant way to achieve this” question and I got some nice ideas that I can take to my co-researchers to discuss.

Thanks again

There is also

which maybe of use for you.

1 Like

E.g.

``````fn1(a) = 3 + a^2
fn2(a)= 5 + a
fn3(a) = 12
arr = [fn1, fn2, fn3]

a=15
x = [f(a) for f in arr]

julia> x
3-element Vector{Int64}:
228
20
12
``````

Edit: alternatively using map

``````y = map(x -> x(a), arr)
``````

As an advice (from my own experience, too) - don’t start with metaprogramming before you are fluent with the language. And then you’d in most cases find out, you don’t need it anyway.

2 Likes

Julia does not have `eval` in local scope, by design. This is in part a reason why julia can be fast & dynamic at the same time, with lots of compilation to native code.

2 Likes

You can use the pipe operator:

``````a .|> f
``````

where `f` is your array of functions.

2 Likes

or

``````ff(fn, x) = fn(x)
ff.(arr, a)
``````
1 Like

Julia (as almost languages nowadays) is lexically scoped, i.e., variables get the values from the lexical context, i.e., the local block in the source code, around the definition of an expression:

``````f = () -> a + b^2 + 20  # f is defined in the global scope, ergo, a and b refer to names in the global scope
``````

Thus, calling `f()` fails in any context as no global bindings for `a` and `b` are available.

``````g = let a = 10
b = 2
() -> a + b^2 + 20  # defined in the context with a=10 and b=2
end
``````

Here, the function `g` closes over the values from the lexical context around its definition – therefore it’s commonly called a closure. Now, no matter in which context it is called `g()` will evaluate to 34. Even if you define different bindings for `a` and `b` in the global scope:

``````julia> a = 3; b = 5
5

julia> g()
34
``````

What you would need to make the example work is dynamic scope. In this case, variables are looked up in the current runtime environment. This can be emulated in Julia using `task_local_storage`:

``````ff = () -> task_local_storage(:a)^3 + 1

task_local_storage(ff, :a, 4)  # Call ff in an environment with a=4, i.e., let a = 4 ff() end if Julia were dynamically scoped
``````

In most cases it is better and easier to pass variables as arguments. If more flexibility is needed, passing a dictionary as suggested by @heliosdrm is a good option – in the end, `task_local_storage` is like a global dictionary that can be accessed from, i.e., is implicitly passed to, any function.

1 Like