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!):

In [1]:
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:

In [2]:
δ2 = 1.0e-18;

Hex and binary literals¶

In [3]:
x = 0xFF
print(x)
255
In [4]:
x = 0b1000
print(x)
8

Operators¶

Binary Operators¶

+, -, *, / does what you expect, and there are a few less common ones:

Expression Name Description
x ^ y power raises x to the yth power
x ÷ y integer divide x / y, truncated to an integer, same as div(a,y)
x % y remainder same as rem(x,y)

In particular, note that Julia uses ^ for exponentiation, not logical operations (and not **, as some other languages).

In [5]:
(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)
In [6]:
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.):

In [7]:
a *= 2
14

Comparative testing of values uses the usual operators (==, >, <, >=, <=) and returns a bool type, which can be true or false:

In [8]:
a >= 8
true
In [9]:
a < δ2
false

The ! operator negates a boolean:

In [10]:
!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)

In [11]:
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:

In [12]:
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:

In [13]:
f = 1.234
typeof(f)
Float64
In [14]:
i = UInt(12335124)
0x0000000000bc3814
In [15]:
typeof(i)
UInt64

Julia will generally handle mixed type arithmetic smoothly and safely (this is done by promoting variables):

In [16]:
f * i
1.5221543015999999e7

But it will generally throw an error if this can't be done safely:

In [17]:
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:

In [18]:
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:

In [19]:
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:

The Julia Type Hierarchy, thanks to Uwe Hernandez Acosta

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:

In [20]:
# "im" is the imaginary number constant
m = 1 + 2im
n = -3 - 3im
m*n
3 - 9im
In [21]:
# // 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!" """

In [22]:
"here is a string"
"here is a string"

One point to note is that strings are concatenated with a * operator:

In [23]:
"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).

In [24]:
текст = """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.

In [25]:
"$m squared is $(m^2)"
"1 + 2im squared is -3 + 4im"

More on strings:

In [26]:
split("one, three, four!", ", ")
3-element Vector{SubString{String}}:
 "one"
 "three"
 "four!"
In [27]:
occursin("sip", "Mississippi")
true
In [28]:
replace("I like tea", "tea" => "coffee and tea")
"I like coffee and tea"

Regular expressions¶

In [29]:
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¶

In [30]:
struct Person
    name
    age
end

p = Person("Mary", 30)
p.age
30

One can also define a constructor:

In [31]:
struct Person2
    name
    age
    function Person2()
        new("--", -1)
    end
end

p2 = Person2()
Person2("--", -1)

One can also define the type of the struct variables:

In [32]:
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.)

In [33]:
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:

In [39]:
@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