Consider this source code
println("Julia language version ",VERSION)
i=666
for i = 1:2
println("i is $i")
end
println("global i is $i")
function main()
j = 666
for j = 1:2
println("j is $j")
end
println("global j is $j")
end
main()
Consider the output of version 0.6
Julia language version 0.6.3
i is 1
i is 2
global i is 2
j is 1
j is 2
global j is 2
Compare to the output of version 1.0
Julia language version 1.0.0
i is 1
i is 2
global i is 666
j is 1
j is 2
global j is 666
I can't change the value of variable i and variable j using a for loop like I can before in version 0.6
I think C programmers will have the shock of their lives...
If you use Julia 0.7 (which is basically == 1.0 with deprecations) you would see the necessary deprecation messages for the intended behaviour change:
┌ Warning: Deprecated syntax `implicit assignment to global variable `i``.
│ Use `global i` instead.
└ # none:0
┌ Warning: Loop variable `i` overwrites a variable in an enclosing scope. In the future the variable will be local to the loop instead.
└ # none:0
i is 1
i is 2
global i is 2
So to get what you want you could write:
function main()
global j = 666
for j = 1:2
println("j is $j")
end
println("global j is $j")
end
main()
Your first example on the global level should theoretically be dealt with using for outer i.. as described in https://docs.julialang.org/en/latest/manual/variables-and-scoping/#For-Loops-and-Comprehensions-1 but currently this is not handled in the REPL. See this issue: https://github.com/JuliaLang/julia/issues/24293
Related
I wondered if there is an equivalent to the browser() statement available in RStudio for debugging purposes for Julia (I am using the Juno IDE at the moment).
The R function browser() halts execution and invokes an environment browser when it is called. So, in principle, we can put browser() anywhere in our code to stop in this particular line and see what's stored in the environment at that moment, which is terrific for debugging purposes.
For instance, the code below will stop when i>3. Hence, that's exactly what we will see in the environment browser available in RStudio, where we will observe that i=4 at that moment in the code.
for (i in 1:5) {
print(i)
if (i>3) {
browser()
}
}
[1] 1
[1] 2
[1] 3
[1] 4
Called from: eval(ei, envir)
Browse[1]>
Have a look at Debugger.jl. Specifically the Place breakpoints in source code section:
It is sometimes more convenient to choose in the source code when to break. This is done for instance in Matlab/Octave with keyboard, and in R with browser(). You can use the #bp macro to do this
Your R example translated to Julia:
julia> using Debugger
julia> #run for i in 1:5
println(i)
if i > 3
#bp
end
end
1
2
3
4
Hit breakpoint:
In ##thunk#257() at REPL[4]:1
9 │ Base.println(i)
10 │ %10 = i > 3
11 └── goto #4 if not %10
●12 3 ─ nothing
>13 4 ┄ #_2 = Base.iterate(%1, %8)
14 │ %14 = #_2 === nothing
15 │ %15 = ($(QuoteNode(Core.Intrinsics.not_int)))(%14)
16 └── goto #6 if not %15
17 5 ─ goto #2
About to run: (iterate)(1:5, 4)
1|debug>
This is a general solution for Julia, Juno IDE also has integrated debugging: Debugging, Juno manual.
Infiltrator.jl's #infiltrate seems like the equivalent in Julia:
julia> using Infiltrator
julia> for i in 1:5
println(i)
if i > 3
#infiltrate
end
end
1
2
3
4
Infiltrating top-level scope at REPL[1]:4:
infil> i
4
Compared to Debugger.jl's breakpoint, this doesn't slow down program execution at all, at the cost of not allowing you to step further into your program.
I have this simple while loop that uses i = 1 as an index.
global i = 1
n = rows
while i <= n
if prod(isa.(collect((y)[i,:]),Number))==0
delete!(y,i)
x_axis = x_axis[1:end .!= i]
n -= 1
end
i += 1
end
but I'm getting this error:
UndefVarError: i not defined
top-level scope#Local: 23
I even made my i global as per the suggestion on some similar questions on SO but the error persists. I am running this on Pluto.jl so maybe it could be an environment issue.
Firstly, note that if you use Julia v1.5+ then you don't need to make i a global (example below with current stable version v1.5.4):
julia> i = 1
1
julia> while i < 5
println(i)
i += 1 # <----- this works in Julia v1.5+
end
1
2
3
4
However, it seems you are using Julia v1.4 of older, in which case I think Logan Kilpatrick gave you the answer: You need to make i a global from inside the while loop's scope. As Logan mentioned, try adding global where you increment i, like in this example from the while function's docs:
julia> i = 1 ;
julia> while i < 5
println(i)
global i += 1 # <------------ Try this!
end
1
2
3
4
Note also that you don't need to specify it's a global if your while loop is inside a function, as in
julia> function foo(istart)
i = istart
while i < 5
println(i)
i += 1 # <-- 'global' not needed inside a function!
end
end
foo (generic function with 1 method)
julia> foo(1)
1
2
3
4
You have hitted the "ambiguous soft scope case".
In short: the assignment of a local variable inside a (soft) local scope depends if the code is inside a "REPL context"
or not.
For "REPL context" I mean the REPL and all environments that behaves as the REPL in this case, for example
Jupyter:
julia> i = 0
julia> while i < 3
i += 1
#info i
end
[ Info: 1
[ Info: 2
[ Info: 3
Instead code from non interactive context like file, eval and Pluto acts like that:
julia> code = """
i = 0
while i < 3
i += 1
#info i
end
"""
julia> include_string(Main, code)
┌ Warning: Assignment to `i` in soft scope is ambiguous because a global variable by the same name exists: `i` will be treated as a new local. Disambiguate by using `local i` to suppress this warning or `global i` to assign to the existing global variable.
└ # string:3
ERROR: LoadError: UndefVarError: i not defined
All of this has been designed to ensure both the convenience of REPL usage and to avoid unwanted side effects of using julia on a large scale.
Full details here.
To fix the problem you may be use global as already suggested or enclose your code inside a function.
Pluto implicitly wraps a cell into a function, see https://github.com/fonsp/Pluto.jl/pull/720, therefore the global annotation or explicit wrapping into a function should not be required.
Putting the following into a Pluto cells works for me:
begin
i = 1
n = 100
while i<=n
if i % 2 == 0
n -= 1
end
i += 1
end
end
The implicit function wrapping is disabled when macros are used inside a cell (which prevents Pluto to gather reactivity information), therefore the following does not work in Pluto due to the Julia scoping rules:
begin
i = 1
n = 100
while i<=n
if i % 2 == 0
n -= 1
end
#show i += 1
end
end
Throws:
UndefVarError: i not defined
top-level scope#Local: 5[inlined]
top-level scope#none:0
I want to overwrite a function in Julia using its old definition. It seems the way to do this would be to clone the function and overwrite the original using the copy — something like the following. However, it appears deepcopy(f) just returns a reference to f, so this doesn't work.
f(x) = x
f_old = deepcopy(f)
f(x) = 1 + f_old(x)
How can I clone a function?
Background: I'm interesting in writing a macro #override that allows me to override functions pointwise (or maybe even piecewise).
fib(n::Int) = fib(n-1) + fib(n-2)
#override fib(0) = 1
#override fib(1) = 1
This particular example would be slow and could be made more efficient using #memoize. There may be good reasons not to do this, but there may also be situations in which one does not know a function fully when it is defined and overriding is necessary.
We can do this using IRTools.jl.
(Note, on newer versions of IRTools, you may need to ask for IRTools.Inner.code_ir instead of IRTools.code_ir.)
using IRTools
fib(n::Int) = fib(n-1) + fib(n-2)
const fib_ir = IRTools.code_ir(fib, Tuple{Int})
const fib_old = IRTools.func(fib_ir)
fib(n::Int) = n < 2 ? 1 : fib_old(fib, n)
julia> fib(10)
89
What we did there was captured the intermediate representation of the function fib, and then rebuilt it into a new function which we called fib_old. Then we were free to overwrite the definition of fib in terms of fib_old! Notice that since fib_old was defined as recursively calling fib, not fib_old, there's no stack overflow when we call fib(10).
The other thing to notice is that when we called fib_old, we wrote fib_old(fib, n) instead of fib_old(n). This is due to how IRTools.func works.
According to Mike Innes on Slack:
In Julia IR, all functions take a hidden extra argument that represents the function itself
The reason for this is that closures are structs with fields, which you need access to in the IR
Here's an implementation of your #override macro with a slightly different syntax:
function _get_type_sig(fdef)
d = splitdef(fdef)
types = []
for arg in d[:args]
if arg isa Symbol
push!(types, :Any)
elseif #capture(arg, x_::T_)
push!(types, T)
else
error("whoops!")
end
end
if isempty(d[:whereparams])
:(Tuple{$(types...)})
else
:((Tuple{$(types...)} where {$(d[:whereparams]...)}).body)
end
end
macro override(cond, fdef)
d = splitdef(fdef)
shadowf = gensym()
sig = _get_type_sig(fdef)
f = d[:name]
quote
const $shadowf = IRTools.func(IRTools.code_ir($(d[:name]), $sig))
function $f($(d[:args]...)) where {$(d[:whereparams]...)}
if $cond
$(d[:body])
else
$shadowf($f, $(d[:args]...))
end
end
end |> esc
end
Now one can type
fib(n::Int) = fib(n-1) + fib(n-2)
#override n < 2 fib(n::Int) = 1
julia> fib(10)
89
The best part is that this is nearly as fast (at runtime, not compile time!) as if we had written the conditions into the original function!
n = 15
fib2(n::Int) = n < 2 ? 1 : fib2(n-1) + fib2(n-2)
julia> #btime fib($(Ref(15))[])
4.239 μs (0 allocations: 0 bytes)
89
julia> #btime fib2($(Ref(15))[])
3.022 μs (0 allocations: 0 bytes)
89
I really don't see why you'd want to do this (there must a better way to get what you want!).
Nonetheless, although not exactly equivalent you can get what you want by using anonymous functions:
julia> f = x->x
#3 (generic function with 1 method)
julia> f_old = deepcopy(f)
#3 (generic function with 1 method)
julia> f = x->1+f_old(x)
#5 (generic function with 1 method)
julia> f(4)
5
While experimenting with Julia 1.0, I've noticed that I can do something like this:
x\y = 1
The REPL then shows:
\ (generic function with 1 method)
which means its a valid assignment (the interpreter doesn't complain). However, x, y, and x\y all remain undefined.
What is the meaning of such expression?
It is a new function definition that (kind of) shadows the left division operator \ in Base, since the left division operator is already defined for some types in Julia. The new function definition is \(x,y) = 1 (the names of function parameters do not matter) which works for all types of variables. This will prevent julia from loading Base.\ due to name conflict. No matter what the input is your new \ will return the same value.
julia> x\y = 5
julia> a = 3; b = 4;
julia> a\b
5
julia> c = "Lorem ipsum"; d = "dolor";
julia> c\d
5
If you have already used the \ that is defined in Base, your redefinition will throw an error saying that extending Base.\ requires an explicit import with import Base.\. The behavior of defining \ after import Base.\ however will be different. It will extend the operator Base.\.
julia> 1\[1,3]
2-element Array{Float64,1}:
1.0
3.0
julia> import Base.\
julia> x\y=3
\ (generic function with 152 methods)
julia> 1\[1,3]
2-element Array{Int64,1}:
3
3
I get different behavior for the same code below for Julia 0.5.0 and 0.6.0
workspace()
rmprocs(workers())
addprocs(2)
r = #spawnat workers()[2] #eval a=20000
println(fetch(r)) # prints 20000 as expected
a = 1 # assign value to a in process 1
r = #spawnat workers()[2] #eval a
println(fetch(r)) # prints 20000 as expected
r = #spawnat(workers()[2], getfield(Main, :a))
println(fetch(r)) # prints 20000 as expected, equivalent to previous fetch
#sync #spawnat workers()[2] println(a) # prints 1 as expected
r = #sync #spawnat workers()[1] #eval a=10000 # define variable a on workers()[1]
#everywhere println(a) # different results on v0.5.0 and v0.6.0
The difference is shown below - namely, that workers()[2] gets a value of a from process 1 which is never explicitly assigned to it. The 0.5.0 code works like I expect it to, and 0.6.0 does not. Any ideas what could be going on here or is it something I don't understand?
v0.5 v0.6
WARNING: rmprocs: process 1 not removed | WARNING: rmprocs: process 1 not removed
20000 | 20000
20000 | 20000
20000 | 20000
From worker 3: 1 | From worker 3: 1
1 | 1
From worker 2: 10000 | From worker 2: 10000
From worker 3: 20000 | From worker 3: 1
I think this has to do with global variables in v0.6.0 onwards.
Global constants (in Module Main) are declared as constants on remote nodes too. If I had used a let statement in the above code as follows:
let a=a
#sync #spawnat workers()[2] println(a) # prints 1 as expected
end
then 0.5.0 and 0.6.0 produce the same result with the #everywhere println(a) on the last line of my previously posted code snippet. I probably need to delve further into channels and futures for parallel code which requires data transfer, initializing variables on parallel processes, etc.