Introduction to the Julia programming language
3 Variables and operators¶
Variables and operators¶
All languages have their syntax and here we present a whistle-stop tour of the highlights of Julia.
We don't attempt to be exhaustive here - check the Julia Documentation (or do a search) if there's anything you are unsure of, and for more functionality we haven't covered here on advanced topics.
Variables¶
A variable is a name bound to a value and assignment is done with =
(no surprise!):
x = 1.09
my_amazing_string = "a string"
δ = 1.0e-9
1.0e-9
Note: In REPL mode, Julia will print the results of the last statement's execution as output. Sometimes you don't want that, in which case it can be suppressed by adding a semicolon at the end of the statement:
δ2 = 1.0e-18;
Hex and binary literals¶
x = 0xFF
print(x)
255
x = 0b1000
print(x)
8
In particular, note that Julia uses ^
for exponentiation, not logical operations (and not **, as some other languages).
(2^10) + (33%10)
1027
Bitwise Operators¶
Bitwise operators are supported on all primitive integer types:
Expression | Name |
---|---|
~x |
bitwise not |
x & y |
bitwise and |
x \| y |
bitwise or |
x ⊻ y |
bitwise xor (exclusive or) |
a = 0b0111
b = 0b1101
println(string(a & b, base=2))
println(string(a | b, base=2))
101 1111
Updating and Testing¶
Most variables (except for const
globals) can be updated, with the usual updating operators (+=
, -=
, *=
, /=
, etc.):
a *= 2
14
Comparative testing of values uses the usual operators (==
, >
, <
, >=
, <=
) and returns a bool
type, which can be true
or false
:
a >= 8
true
a < δ2
false
The !
operator negates a boolean:
!true
false
Julia also has a ===
comparison operator that is true
when two objects are the same (not just the same value(s), but really the same object)
a = [1, 2]
b = [1, 2]
c = a
@show a === b
@show a === c
a === b = false a === c = true
true
Julia allows you to chain comparisons:
1 < 2 <= 2 < 3 == 3 > 2 >= 1 == 1 < 3 != 5
true
Basic Types¶
Julia supports integers (signed and unsigned) and floats, all with varying bit widths:
f = 1.234
typeof(f)
Float64
i = UInt(12335124)
0x0000000000bc3814
typeof(i)
UInt64
Julia will generally handle mixed type arithmetic smoothly and safely (this is done by promoting variables):
f * i
1.5221543015999999e7
But it will generally throw an error if this can't be done safely:
Int16(i)
InexactError: trunc(Int16, 12335124) Stacktrace: [1] throw_inexacterror(f::Symbol, ::Type{Int16}, val::UInt64) @ Core ./boot.jl:634 [2] checked_trunc_sint @ ./boot.jl:656 [inlined] [3] toInt16 @ ./boot.jl:687 [inlined] [4] Int16(x::UInt64) @ Core ./boot.jl:782 [5] top-level scope @ ~/uni/Vorlesungen/Julia-Course-Uni-HD/julia-03-operators-variables.ipynb:1
As in Python, you don't need to specify a type for a variable (unlike C++) - by default, they work like Python, and can freely be assigned any values of any type.
However, you can specify a type for a variable, using the ::Type
notation, which causes that variable to only hold values of that type:
integer_value::Int64 = 6;
integer_value = 2.0;
@show integer_value
typeof(integer_value)
integer_value = 2
Int64
As we noted above, Julia will only perform "free" conversions of type if it can do so without losing precision. If we try to put a non-integer into this variable:
integer_value = 2.5
InexactError: Int64(2.5) Stacktrace: [1] Int64 @ ./float.jl:912 [inlined] [2] convert(::Type{Int64}, x::Float64) @ Base ./number.jl:7 [3] top-level scope @ ~/uni/Vorlesungen/Julia-Course-Uni-HD/julia-03-operators-variables.ipynb:1
Julia's types are arranged in a "hierarchy", from more general to more specific types:
Only the "leaves" of the type tree can be values ("concrete types") - but variables (and functions) can be defined in terms of any of the types, even the "abstract" ones. This means you can, for example, define a variable that will hold any "Number" type - allowing it to hold Float, Rational or Integer values, but not, say, Strings.
We'll come back to other aspects of this when we cover multiple dispatch.
Complex and Rational Numbers¶
Complex and rational numbers are handled natively in Julia:
# "im" is the imaginary number constant
m = 1 + 2im
n = -3 - 3im
m*n
3 - 9im
# // defines a rational
r1 = 2//3
r2 = 1//4
r1*r2
1//6
Strings¶
Strings in Julia are defined with double quotes:
"oh yes, they are"
or triple double quotes, which allow use of unescaped double quotes and newlines:
"""Sam exclaimed, "this is much easier than using backslashes!" """
"here is a string"
"here is a string"
One point to note is that strings are concatenated with a *
operator:
"hello " * "world"
"hello world"
Strings are internally represented as UTF-8, so they can hold any Unicode values. However, they are indexed by byte, and attempting to take values from the middle of a code-point will cause an error. There are methods which provide iterators to safely iterate over the individual "characters" if you need to.
Characters are defined with single quotes: 'प'
, and are UTF-32 (32-bit values).
текст = """The Ukrainian for "text" is "текст" and the Hindi is "पाठ"!"""
#trying to take from position 37 would cause an error, as Cyrillic chars are two-bytes wide.
текст[38] == 'т'
true
Finally, Julia supports string interpolation using $
to specify the variable, or expression if contained within parentheses, to substitute into the string.
"$m squared is $(m^2)"
"1 + 2im squared is -3 + 4im"
More on strings:
split("one, three, four!", ", ")
3-element Vector{SubString{String}}: "one" "three" "four!"
occursin("sip", "Mississippi")
true
replace("I like tea", "tea" => "coffee and tea")
"I like coffee and tea"
Regular expressions¶
regex = r"^[a-z]{2}\d{3}$"
str = "ab123"
if occursin(regex, str)
println("Valid Uni-Id")
else
println("Not a valid Uni-ID")
end
Valid Uni-Id
Composite Types¶
struct Person
name
age
end
p = Person("Mary", 30)
p.age
30
One can also define a constructor:
struct Person2
name
age
function Person2()
new("--", -1)
end
end
p2 = Person2()
Person2("--", -1)
One can also define the type of the struct variables:
struct my_vector
x::Float64
y::Float64
end
υϵκτωρ = my_vector(3.7, 6e7)
my_vector(3.7, 6.0e7)
By default, struct types are immutable - you can't update their elements - but you can explicitly make mutable ones. (You can also, as in C++, make "templated" structs, defining the types of the elements via a placeholder - which is also how Arrays work internally.)
mutable struct my_mut_vector
x::Float64
y::Float64
end
μ_υϵκτωρ = my_mut_vector(3.7, 6e7)
μ_υϵκτωρ.x = 6.0
μ_υϵκτωρ
my_mut_vector(6.0, 6.0e7)
Enums¶
Enumerations make the code more readable:
@enum Fruit apple=1 banana=2 orange=3
using Random
f = Fruit(rand(1:3))
if (f == apple)
println("We got an apple")
else
println("We didn't get an apple")
end
We got an apple