Trouble with @eval with mixed variable scopes

Hi there. I have found some behavior of the “@eval”-macro that I cant quite explain.
So, as far as I understand it, “@eval expr” takes a segment of code, and evaluates it as if it had been written at that point in the sourcecode. But if “expr” mixes the global variable-scope with a local one, it seems to not work properly.
An example:

@eval begin
    x=1;
    x+=1
    println(x)
end
println("this worked!")

This returns

2
this worked!

, as is expected. But if I write

@eval begin
    x=1;
    while x<2
        x+=1
    end
    println(x)
end
println("This did not work!")

, then I get

UndefVarError: x not defined
Stacktrace:
[1] top-level scope at .\In[2]:4
[2] eval(::Module, ::Any) at .\boot.jl:330

It seems that the x in x+=1 in the scope of the while-loop is no longer recognized. The same happens with a for-loop instead of “while”.
If I just remove the “@eval” it works of course as intended
I can also fix this by encapsulating the x in its own scope:

@eval begin
    let x=1
        while x<2
            x+=1
        end
        println(x)
    end
end
println("This works again!")

this, again, results in

2
This works again!

As I see it, all three versions should return 2. Of course I can work around this, but I would like to know whether this is a bug, or, if not, why this happens.

Notes:
-I tried this with versions 1.2.0 and 1.3.1, in both a shell and a notebook, with identical results.

-this is not a purely artifical problem, but a reduced form of an error with my Script for data-evaluation. When I include(...) that Script I run into exactly the same error, and include - as i understand it - uses the eval-function on whatever is provided as an argument.

@eval works in global scope. A while loop introduces a local scope, in which global variables cannot be changed unless you use global:

julia> begin
    x=1;
    while x<2
        global x+=1
    end
    println(x)
end
2
julia> x
2

let introduces a local scope. Inside a local scope variables can be modified within for or while. Therefore your 3rd example works. But, if you restart the repl and do

julia> begin
    let x=1
        while x<2
            x+=1
        end
        println(x)
    end
end
2
julia> x
ERROR: UndefVarError: x not defined

See the Julia manual on this.

2 Likes

I understand local and global scopes, thats not the point. My question concerns the behavior of the macro. Evaluating either of my three code-snippets (in a global scope) should yield the same result with or without the macro (deleting the @eval should not change anything). This holds for example 1 and 3 (without loop and with wrapped loop). But the second example (the unwrapped while-loop) breaks if evaluated with the macro, and works just fine without it.

No, your second example does not work without the @eval:

julia> begin
           x=1;
           while x<2
               x+=1
           end
           println(x)
       end
       println("This did not work!")
ERROR: UndefVarError: x not defined
Stacktrace:
 [1] top-level scope at ./REPL[2]:4

I tried it on 1.1 and 1.2.

Interesting…
I usually work in Jupyter Lab.

I’ve tried exacly this code in a fresh jupyter notebook, and it works (returns 2, no error)! I tried the same in the jupyter console, and it still works! I tried it in an ordinary julia console, and now exactly the same code breaks (UndefVarError, as in your example)!
So it seems this arises from the way that jupyter handles its scopes?

(Btw could someone point me to a tutorial on how to include these console-commands+output? Im new here and have not figured this out yet)

I am not too familiar with Jupyter, but I think it changes the scope behaviour by default (see GitHub - JuliaLang/IJulia.jl: Julia kernel for Jupyter). There it is also explained how to get the raw REPL scope experience :slight_smile:

Regarding the code blocks have a look at Please read: make it easier to help you.

1 Like

@thofma
:+1: Ty, I was not aware of that “softscope”-thing. This explains it though. Since I had trouble before with accidental mixing of scopes I think I will opt out of this in the future.

For what it’s worth, in Julia 1.5 the REPL will behave the same way as IJulia currently does (aka soft scope), while in non-interactive contexts (files, eval) there will be a warning telling you that you need to disambiguate the inner x by explicitly declaring it as local or global.

1 Like