Trying to understand Julia macro #isdefined - julia

Hi I'm trying to understand how the macro #isdefined works.
I was expecting Chunk 1 to print out 1 2 3 4, but it is not printing anything.
Also related, I was expecting chunk 2 to print out 2 3 4 5, but it is throwing an error: "a is not defined".
# Chunk 1
for i = 1:5
if #isdefined a
print(a)
end
a = i
end
# Chunk 2
for i = 1:5
if i > 1
print(a)
end
a = i
end
Could someone help explain what is wrong about each chunk? Thank you.

The reason is that a is a local variable in the scope of for loop. Now the crucial part is that for loop follows the following rule defined here:
for loops, while loops, and comprehensions have the following behavior: any new variables introduced in their body scopes are freshly allocated for each loop iteration
This means that assignment to a at the end of the loop does not carry over to the next iteration, because when the new iteration starts the old value of a is discarded as a is freshly allocated. It only gets defined after a=i assignment.
Therefore you have the following behavior:
julia> for i = 1:5
if #isdefined a
println("before: ", a)
end
a = i
if #isdefined a
println("after: ", a)
end
end
after: 1
after: 2
after: 3
after: 4
after: 5
However, if a is defined in an outer scope, then its value is not for loop local and is preserved between iterations, so you have for instance:
julia> let a
for i = 1:5
if #isdefined a
println("before: ", a)
end
a = i
if #isdefined a
println("after: ", a)
end
end
end
after: 1
before: 1
after: 2
before: 2
after: 3
before: 3
after: 4
before: 4
after: 5
and
julia> let a
for i = 1:5
if i > 1
println(a)
end
a = i
end
end
1
2
3
4
I have used let block but it could be any kind of outer scope except global scope (in which case you would have to change a = i to global a = i to get the same effect).

Related

Julia throws undefined error when the variable is defined

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

Multiple outputs used repeatedly in for loop Julia

I am using Julia and I've designed a for loop that takes the outputs of a function in one loop and uses them as the input of that function in the next loop (and over and over). When I run this code, Julia flags an "undefined" error, however, if I run the code in debug mode, it executes perfectly. For example, the code looks like this:
function do_command(a,b,c,d)
a = a + 1
b = split(b, keepempty=false)[1]
c = split(b, keepempty=false)[1]
if a == 1000
d = true
else
d = false
end
return a, b, c, d
end
for ii in 1:length(x)
if ii == 1
a = 0
b = "string something"
c = ""
d = false
end
a,b,c,d = do_command(a,b,c,d)
if d == true
print(string(b))
break
end
end
What am I doing wrong here?
An issue with your code is that for introduces a new scope for each iteration of the loop. That is to say: variable a created within the loop body at iteration 1 is not the same as variable a created within the loop body at iteration 2.
In order to fix your problem, you should declare variables outside the loop, so that at each iteration, references to them from within the loop body would actually refer to the same variables from the enclosing scope.
I'd go with something like this:
function do_command(a,b,c,d)
a = a + 1
b = split(b, keepempty=false)[1]
c = split(b, keepempty=false)[1]
if a == 1000
d = true
else
d = false
end
return a, b, c, d
end
# Let's create a local scope: it's good practice to avoid global variables
let
# All these variables are declared in the scope introduced by `let`
a = 0
b = "string something"
c = ""
d = false
for ii in 1:10 #length(x)
# now these names refer to the variables declared in the enclosing scope
a,b,c,d = do_command(a,b,c,d)
if d == true
print(string(b))
break
end
end
end

Problem with declaring and changing the value of a variable in julia

What is wrong with my code? Do I have to declare x before using it?
function f(n::Int64, t::Int64)
A = ones(n,n)
for i=0:t
if i > 0
A[x,a] = rand()*A[x,a] + rand()
end
y = rand(1:n)
b = rand(1:n)
if i > 0
A[x,a] = rand()*A[x,a] + rand()*A[y,b]
end
x = y
a = min(b, rand(1:n))
end
return A
end
Here is the error thrown when trying to call f:
UndefVarError: x not defined
I think that the reason is more complex, as similar code in Python would work.
For example compare (Python):
>>> def f():
... for i in range(3):
... if i > 0:
... print(a)
... a = i
...
>>> f()
0
1
to (Julia):
julia> function f()
for i in 0:2
if i > 0
println(a)
end
a = i
end
end
f (generic function with 1 method)
julia> f()
ERROR: UndefVarError: a not defined
So what is the difference? As the Julia manual explains here you have:
for loops, while loops, and comprehensions have the following behavior: any new variables introduced in their body scopes are freshly allocated for each loop iteration, as if the loop body were surrounded by a let block
This means that in your code variables a and x as they are local to the for loop are freshly allocated in each iteration of the loop. Because of this the variable has to be assigned to before it is accessed inside the loop.
Therefore it is not needed to assign a value to x and a before the loop. It is enough to define them in scope outer to the loop (even without assigning of the value). For example like this:
julia> function f(n::Int64, t::Int64)
A = ones(n,n)
local x, a
for i=0:t
if i > 0
A[x,a] = rand()*A[x,a] + rand()
end
y = rand(1:n)
b = rand(1:n)
if i > 0
A[x,a] = rand()*A[x,a] + rand()*A[y,b]
end
x = y
a = min(b, rand(1:n))
end
return A
end
f (generic function with 1 method)
julia> f(1,1)
1×1 Array{Float64,2}:
0.94526289614139
Now it works because x and a are not freshly allocated in each iteration of the loop.
In my original toy example it would look like:
julia> function f()
local a
for i in 0:2
if i > 0
println(a)
end
a = i
end
end
f (generic function with 2 methods)
julia> f()
0
1
and you see that you get exactly what you had in Python.

Can I use <- instead of = in Julia?

Like in R:
a <- 2
or even better
a ← 2
which should translate to
a = 2
and if possible respect method overloading.
= is overloaded (not in the multiple dispatch sense) a lot in Julia.
It binds a new variable. As in a = 3. You won't be able to use ← instead of = in this context, because you can't overload binding in Julia.
It gets lowered to setindex!. As in, a[i] = b gets lowered to setindex!(a, b, i). Unfortunately, setindex! takes 3 variables while ← can only take 2 variables. So you can't overload = with 3 variables.
But, you can use only 2 variables and overload a[:] = b, for example. So, you can define ←(a,b) = (a[:] = b) or ←(a,b) = setindex!(a,b,:).
a .= b gets lowered to (Base.broadcast!)(Base.identity, a, b). You can overload this by defining ←(a,b) = (a .= b) or ←(a,b) = (Base.broadcast!)(Base.identity, a, b).
So, there are two potentially nice ways of using ←. Good luck ;)
Btw, if you really want to use ← to do binding (like in 1.), the only way to do it is using macros. But then, you will have to write a macro in front of every single assignment, which doesn't look very good.
Also, if you want to explore how operators get lowered in Julia, do f(a,b) = (a .= b), for example, and then #code_lowered f(x,y).
No. = is not an operator in Julia, and cannot be assigned to another symbol.
Disclaimer: You are fully responsible if you will try my (still beginner's) experiments bellow! :P
MacroHelper is module ( big thanks to #Alexander_Morley and #DanGetz for help ) I plan to play with in future and we could probably try it here :
julia> module MacroHelper
# modified from the julia source ./test/parse.jl
function parseall(str)
pos = start(str)
exs = []
while !done(str, pos)
ex, pos = parse(str, pos) # returns next starting point as well as expr
ex.head == :toplevel ? append!(exs, ex.args) : push!(exs, ex)
end
if length(exs) == 0
throw(ParseError("end of input"))
elseif length(exs) == 1
return exs[1]
else
return Expr(:block, exs...) # convert the array of expressions
# back to a single expression
end
end
end;
With module above you could define simple test "language":
julia> module TstLang
export #tst_cmd
import MacroHelper
macro tst_cmd(a)
b = replace("$a", "←", "=") # just simply replacing ←
# in real life you would probably like
# to escape comments, strings etc
return MacroHelper.parseall(b)
end
end;
And by using it you could probably get what you want:
julia> using TstLang
julia> tst```
a ← 3
println(a)
a +← a + 3 # maybe not wanted? :P
```
3
9
What about performance?
julia> function test1()
a = 3
a += a + 3
end;
julia> function test2()
tst```
a ← 3
a +← a + 3
```
end;
julia> test1(); #time test1();
0.000002 seconds (4 allocations: 160 bytes)
julia> test2(); #time test2();
0.000002 seconds (4 allocations: 160 bytes)
If you like to see syntax highlight (for example in atom editor) then you need to use it differently:
function test3()
#tst_cmd begin
a ← 3
a ← a + a + 3 # parser don't allow you to use "+←" here!
end
end;
We could hope that future Julia IDEs could syntax highlight cmd macros too. :)
What could be problem with "solution" above? I am not so experienced julian so many things. :P (in this moment something about "macro hygiene" and "global scope" comes to mind...)
But what you want is IMHO good for some domain specific languages and not to redefine basic of language! It is because readability very counts and if everybody will redefine everything then it will end in Tower of Babel...

Getting count of occurrences for X in string

Im looking for a function like Pythons
"foobar, bar, foo".count("foo")
Could not find any functions that seemed able to do this, in a obvious way. Looking for a single function or something that is not completely overkill.
Julia-1.0 update:
For single-character count within a string (in general, any single-item count within an iterable), one can use Julia's count function:
julia> count(i->(i=='f'), "foobar, bar, foo")
2
(The first argument is a predicate that returns a ::Bool).
For the given example, the following one-liner should do:
julia> length(collect(eachmatch(r"foo", "bar foo baz foo")))
2
Julia-1.7 update:
Starting with Julia-1.7 Base.Fix2 can be used, through ==('f') below, as to shorten and sweeten the syntax:
julia> count(==('f'), "foobar, bar, foo")
2
What about regexp ?
julia> length(matchall(r"ba", "foobar, bar, foo"))
2
I think that right now the closest built-in thing to what you're after is the length of a split (minus 1). But it's not difficult to specifically create what you're after.
I could see a searchall being generally useful in Julia's Base, similar to matchall. If you don't care about the actual indices, you could just use a counter instead of growing the idxs array.
function searchall(s, t; overlap::Bool=false)
idxfcn = overlap ? first : last
r = findnext(s, t, firstindex(t))
idxs = typeof(r)[] # Or to only count: n = 0
while r !== nothing
push!(idxs, r) # n += 1
r = findnext(s, t, idxfcn(r) + 1)
end
idxs # return n
end
Adding an answer to this which allows for interpolation:
julia> a = ", , ,";
julia> b = ",";
julia> length(collect(eachmatch(Regex(b), a)))
3
Actually, this solution breaks for some simple cases due to use of Regex. Instead one might find this useful:
"""
count_flags(s::String, flag::String)
counts the number of flags `flag` in string `s`.
"""
function count_flags(s::String, flag::String)
counter = 0
for i in 1:length(s)
if occursin(flag, s)
s = replace(s, flag=> "", count=1)
counter+=1
else
break
end
end
return counter
end
Sorry to post another answer instead of commenting previous one, but i've not managed how to deal with code blocks in comments :)
If you don't like regexps, maybe a tail recursive function like this one (using the search() base function as Matt suggests) :
function mycount(what::String, where::String)
function mycountacc(what::String, where::String, acc::Int)
res = search(where, what)
res == 0:-1 ? acc : mycountacc(what, where[last(res) + 1:end], acc + 1)
end
what == "" ? 0 : mycountacc(what, where, 0)
end
This is simple and fast (and does not overflow the stack):
function mycount2(where::String, what::String)
numfinds = 0
starting = 1
while true
location = search(where, what, starting)
isempty(location) && return numfinds
numfinds += 1
starting = location.stop + 1
end
end
one liner: (Julia 1.3.1):
julia> sum([1 for i = eachmatch(r"foo", "foobar, bar, foo")])
2
Since Julia 1.3, there has been a count method that does exactly this.
count(
pattern::Union{AbstractChar,AbstractString,AbstractPattern},
string::AbstractString;
overlap::Bool = false,
)
Return the number of matches for pattern in string.
This is equivalent to calling length(findall(pattern, string)) but more
efficient.
If overlap=true, the matching sequences are allowed to overlap indices in the
original string, otherwise they must be from disjoint character ranges.
│ Julia 1.3
│
│ This method requires at least Julia 1.3.
julia> count("foo", "foobar, bar, foo")
2
julia> count("ana", "bananarama")
1
julia> count("ana", "bananarama", overlap=true)
2

Resources