I'm trying to understand the scope rules in Julia. I've tried a for loop with z as a regular integer variable:
z = 2
for i = 1:4
z += 1
end
println(z)
and it gives me an UndefVarError: z not defined error, unless I put global z inside of the loop.
However, if I make z a 1x1 array it works perfectly fine without global:
z = [2]
for i = 1:4
z .+= 1
end
println(z)
$ julia test.jl
[6]
What's the difference between scopes of arrays and variables?
The contents of an array assigned to a global variable are constant in type:
julia> a = [1]
1-element Array{Int64,1}:
1
julia> push!(a, "s")
ERROR: MethodError: Cannot `convert` an object of type String to an
object of type Int64
And const typed globals are OK to be referenced within loops.
Related
syntax: invalid type signature around C:\Users\admin.julia\pluto_notebooks\Revolutionary blueprint.jl#==#396279cf-6e6b-468e-8aa6-772e2279058d:1
enter image description here
enter image description here
You cannot use a dash as part of a variable name, as it is the subtraction operator:
julia> x = 5
5
julia> y = 2
2
julia> x-y
3
So there's no way to distinguish x minus y from a variable called "x-y". Use underscores instead:
julia> struct My_Struct end
julia> struct My-Struct end
ERROR: syntax: invalid type signature around REPL[47]:1
Stacktrace:
[1] top-level scope
# REPL[47]:1
I want to lock the type of a variable in Julia, how to do? For example, I define an array called weight,
weight = Array{Float64,1}([1,2,3])
Now I want to lock the type of weight as Array{Float64,1}, is it possible?
I mean if do not lock the type of weight, then if I mistakenly or casually do
weight = 1
Then weight will become an Int64 variable, so it is not longer a 1D array. This is obviously not what I want.
I just want to make sure that once I defined weight as 1D Float64 array, then if I change the type of weight, I want Julia gives me an error saying that the type of weight has been changed which is not allowed. Is it possible? Thanks!
This is useful because by doing this, it may preventing me from forgetting weight is an 1D array, and therefore preventing bugs.
For global variables use const:
julia> const weight = Array{Float64,1}([1,2,3])
3-element Vector{Float64}:
1.0
2.0
3.0
julia> weight[1]=11
11
julia> weight=99
ERROR: invalid redefinition of constant weight
Note that redefining the reference will throw a warning:
julia> const u = 5
5
julia> u=11
WARNING: redefinition of constant u. This may fail, cause incorrect answers, or produce other errors
You can circumvent it by using the Ref type:
julia> const z = Ref{Int}(5)
Base.RefValue{Int64}(5)
julia> z[] = 11
11
julia> z[] = "hello"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int64
In functions use local with type declaration:
julia> function f()
local a::Int
a="hello"
end;
julia> f()
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int64
You'd normally write:
weight::Vector{Float64} = Array{Float64,1}([1,2,3])
...but this doesn't seem to be possible in global scope:
julia> weight::Vector{Float64} = Array{Float64,1}([1,2,3])
ERROR: syntax: type declarations on global variables are not yet supported
Stacktrace:
[1] top-level scope
# REPL[8]:1
However, you can do it in local scope or in a struct:
julia> function fun()
weight::Vector{Float64} = Array{Float64,1}([1,2,3])
weight = 1
end
fun (generic function with 1 method)
julia> fun()
ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type Vector{Float64}
Closest candidates are:
convert(::Type{T}, ::AbstractArray) where T<:Array at array.jl:532
convert(::Type{T}, ::LinearAlgebra.Factorization) where T<:AbstractArray at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/LinearAlgebra/src/factorization.jl:58
convert(::Type{T}, ::T) where T<:AbstractArray at abstractarray.jl:14
...
Stacktrace:
[1] fun()
# Main ./REPL[10]:3
[2] top-level scope
# REPL[11]:1
You could use const, but then redefinition with a value of the same type will cause a warning:
julia> const weight = Array{Float64,1}([1,2,3]);
julia> weight = [2.]
WARNING: redefinition of constant weight. This may fail, cause incorrect answers, or produce other errors.
1-element Vector{Float64}:
2.0
I'm trying to build a function that will output an expression to be assigned to a new in-memory function. I might be misinterpreting the capability of metaprogramming but, I'm trying to build a function that generates a math series and assigns it to a function such as:
main.jl
function series(iter)
S = ""
for i in 1:iter
a = "x^$i + "
S = S*a
end
return chop(S, tail=3)
end
So, this will build the pattern and I'm temporarily working with it in the repl:
julia> a = Meta.parse(series(4))
:(x ^ 1 + x ^ 2 + x ^ 3 + x ^ 4)
julia> f =eval(Meta.parse(series(4)))
120
julia> f(x) =eval(Meta.parse(series(4)))
ERROR: cannot define function f; it already has a value
Obviously eval isn't what I'm looking for in this case but, is there another function I can use? Or, is this just not a viable way to accomplish the task in Julia?
The actual error you get has to do nothing with metaprogramming, but with the fact that you are reassigning f, which was assigned a value before:
julia> f = 10
10
julia> f(x) = x + 1
ERROR: cannot define function f; it already has a value
Stacktrace:
[1] top-level scope at none:0
[2] top-level scope at REPL[2]:1
It just doesn't like that. Call either of those variables differently.
Now to the conceptual problem. First, what you do here is not "proper" metaprogramming in Julia: why deal with strings and parsing at all? You can work directly on expressions:
julia> function series(N)
S = Expr(:call, :+)
for i in 1:N
push!(S.args, :(x ^ $i))
end
return S
end
series (generic function with 1 method)
julia> series(3)
:(x ^ 1 + x ^ 2 + x ^ 3)
This makes use of the fact that + belongs to the class of expressions that are automatically collected in repeated applications.
Second, you don't call eval at the appropriate place. I assume you meant to say "give me the function of x, with the body being what series(4) returns". Now, while the following works:
julia> f3(x) = eval(series(4))
f3 (generic function with 1 method)
julia> f3(2)
30
it is not ideal, as you newly compile the body every time the function is called. If you do something like that, it is preferred to expand the code once into the body at function definition:
julia> #eval f2(x) = $(series(4))
f2 (generic function with 1 method)
julia> f2(2)
30
You just need to be careful with hygiene here. All depends on the fact that you know that the generated body is formulated in terms of x, and the function argument matches that. In my opinion, the most Julian way of implementing your idea is through a macro:
julia> macro series(N::Int, x)
S = Expr(:call, :+)
for i in 1:N
push!(S.args, :($x ^ $i))
end
return S
end
#series (macro with 1 method)
julia> #macroexpand #series(4, 2)
:(2 ^ 1 + 2 ^ 2 + 2 ^ 3 + 2 ^ 4)
julia> #series(4, 2)
30
No free variables remaining in the output.
Finally, as has been noted in the comments, there's a function (and corresponding macro) evalpoly in Base which generalizes your use case. Note that this function does not use code generation -- it uses a well-designed generated function, which in combination with the optimizations results in code that is usually equal to the macro-generated code.
Another elegant option would be to use the multiple-dispatch mechanism of Julia and dispatch the generated code on type rather than value.
#generated function series2(p::Val{N}, x) where N
S = Expr(:call, :+)
for i in 1:N
push!(S.args, :(x ^ $i))
end
return S
end
Usage
julia> series2(Val(20), 150.5)
3.5778761722367333e43
julia> series2(Val{20}(), 150.5)
3.5778761722367333e43
This task can be accomplished with comprehensions. I need to RTFM...
https://docs.julialang.org/en/v1/manual/arrays/#Generator-Expressions
I am coming from Fortran and I use global vectors of data over the full program. Usually I declare a module:
module xyz
real, allocatable :: vector(:,:,:) ! a 3 dim vector, undefined
end module
Now, in some place, let say Subroutine (Function) A, I am allocating memory for it and initialize to some values:
allocate(vector(10,20,30))
vector = ran()
Now, in any other unit of the program (Subroutine or function B, C, D...), if I am using the module, i.e:
using xyz
The above declared vector is available.
I was not able to obtain this behavior in the new technological wonder Julia 1.1. The scope rules are just giving a headache.
In Julia the rules for accessing variables from other modules are explained in detail here.
The key issues in your situation are the following things:
a variable is visible after using only if it is exported in the module
you are allowed to access the variable in other modules
you are not allowed to rebind a variable from other modules
This means that global variable binding creation operation is private to the module.
Here is a simple example module definition:
module M
export x
x = Int[]
function rebindx()
global x = Int[]
end
end
now assume you define and later use it in REPL (it could be any other module)
julia> module M
export x
x = Int[]
function rebindx()
global x = Int[]
end
end
Main.M
julia> using .M
Now you can access x:
julia> x
0-element Array{Int64,1}
julia> push!(x, 1)
1-element Array{Int64,1}:
1
julia> x
1-element Array{Int64,1}:
1
julia> x[1] = 10
10
julia> x
1-element Array{Int64,1}:
10
But not rebind x:
julia> x = 0
ERROR: cannot assign variable M.x from module Main
However, you can call a function defined inside M module to change the binding of x like this:
julia> x
1-element Array{Int64,1}:
10
julia> M.rebindx()
0-element Array{Int64,1}
julia> x
0-element Array{Int64,1}
This was possible because rebindx was defined inside module M so it has the right to change the binding of variable x defined in this module.
I want to push a SVector (provided by JuliaArrays/StaticArrays.jl) into a vector of SVector. The following code is my trial:
using StaticArrays
lst = Vector{SVector{2, Float64}}[]
a = SVector(1, 2)
push!(lst, a)
But it causes the following error:
ERROR: LoadError: MethodError: Cannot `convert` an object of type Int64 to an object of type SArray{Tuple{2},Float64,1,2}
How can I fix it?
The mistake you are making is that you create an Array of Array of an SVector. T[] creates an empty array of type T.
# This creates an empty array of type Float64
julia> lst = Float64[]
0-element Array{Float64,1}
# This creates an empty array of a Float64 array
julia> lst = Vector{Float64}[]
0-element Array{Array{Float64,1},1}
So you need to redefine your array as an array of SVector.
julia> lst = SVector{2, Float64}[] # an empty 1D array(i.e. Vector) of `SVector`
0-element Array{SArray{Tuple{2},Float64,1,2},1}
julia> a = SVector(1, 2)
2-element SArray{Tuple{2},Int64,1,2}:
1
2
julia> push!(lst, a)
1-element Array{SArray{Tuple{2},Float64,1,2},1}:
[1.0, 2.0]
You can also use this instead of your way of empty array definition:
lst = Vector{SVector{2, Float64}}(undef, 0) # this creates a `Vector` of `SVector` of size 0 (empty)