18
i = 50
function test()
  i = 10
  eval(:i)
end
test()  # => 50

Why does this evaluate to the global i instead of the local one? Is there a way to make it evaluate to the local?

Sean Mackesey
  • 10,701
  • 11
  • 40
  • 66

3 Answers3

16

You can't. Julia's eval always evaluates code the current module's scope, not your local scope. Calling eval in local scope is an anti-pattern and a performance killer. You can, however, construct a new function which includes a bit of user code that refers to local scope and then call that function. For example:

# user-supplied expression
expr = :(i^2 + 1)

# splice into function body
test = @eval function ()
    i = 10
    $expr
end

Now you can call test:

julia> test()
101

Why is this better than calling eval inside of test as in the question? Because in the original, eval needs to be called every time you call test whereas in this usage, eval is only called once when defining test; when test is called no eval is done. So while you may call eval "at run time" in the sense of "while your application is running, getting user input" it is not called "at run time" in the sense of when the function constructed with that user input is called. The former is fine, whereas the latter as distinctly an anti-pattern.

StefanKarpinski
  • 32,404
  • 10
  • 86
  • 111
  • 2
    I keep seeing people say this (also in the JavaScript community). I write a lot of development tools, like code that evaluates users' code at runtime and try as I might, I can't figure out how to do these things without using eval at runtime. I've also contributed code to JavaCall that uses eval at run time in conjunction with Java's introspection at runtime to generate functions to call into Java and I can't see a good way to do this without using eval at run time. Maybe I'm missing something but blanket statements that "eval at run time is an anti-pattern" seem unfounded to me. – Bill Burdick Nov 25 '19 at 14:06
  • 3
    I've expanded on my answer to improve it. Thanks for prompting me to do so, the original answer was too terse and not very helpful. Hopefully this is better. – StefanKarpinski Nov 28 '19 at 14:10
8

As @StefanKarpinski mentioned eval always evaluates in global scope, but if one really wants to evaluate something locally, there are various way to do it:

import Base.Cartesian.lreplace
i = 50
function test1(expr)
  i=10
  eval(lreplace(expr,:i,i))
end

i = 50
function test2()
  i = 10
  @eval $i
end
test1(:(i))  # => 10
test2()      # => 10

But my preferred method to evaluates an expression at run-time is to create a function, I think it's the most efficient:

exprtoeval=:(x*x)
@eval f(x)=$exprtoeval
f(4) # => 16
Reza Afzalan
  • 5,646
  • 3
  • 26
  • 44
3

Depending on the application, you could eval the entire function to get at the local value of i such as @simonster describes in this answer.

Community
  • 1
  • 1
JKnight
  • 1,996
  • 2
  • 14
  • 23