Operators and Variables#
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;
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 |
x % y |
remainder |
same as |
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 |
---|---|
|
bitwise not |
|
bitwise and |
|
bitwise or |
|
bitwise xor (exclusive or) |
a = 7
b = 13
@show a & b
@show a | b;
a & b = 5
a | b = 15
Here we’re also using the @show
macro, which explicitly prints the result of an expression.
Teaser: We’ll look at a very cool feature of vectorised or broadcast operators shortly.
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, #unused#::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
@ In[15]: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:900 [inlined]
[2] convert(#unused#::Type{Int64}, x::Float64)
@ Base ./number.jl:7
[3] top-level scope
@ In[17]: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, because concatenation is not commutative:
"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 "पाठ"!"""
текст[38] == 'т' #trying to take from position 37 would cause an error, as Cyrillic chars are two-bytes wide.
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"
Arrays, Dicts and other Composite Types#
Julia supports Array, Dict, Tuple and other “container types” you may recognise from Python, and the syntax is similar for 1-dimensional examples.
Arrays are rich enough that they deserve their own notebook, which we’ll come to afterward.
Dictionaries#
We can make Dictionaries (for C++ programmers - HashMaps or AssociativeArrays) with the Dict
constructor.
Unlike Python, the mapping between a key and a value is denoted with a =>
.
farmyard_sounds = Dict("🐮" => "moo", "sheep" => "baa", "pig" => "oink", "farmer" => "get off my land!")
@show farmyard_sounds["🐮"]
@show haskey(farmyard_sounds,"pig")
@show farmyard_sounds["🐱"] = "Meow" ;
farmyard_sounds
farmyard_sounds["🐮"] = "moo"
haskey(farmyard_sounds, "pig") = true
farmyard_sounds["🐱"] = "Meow" = "Meow"
Dict{String, String} with 5 entries:
"farmer" => "get off my land!"
"🐱" => "Meow"
"pig" => "oink"
"🐮" => "moo"
"sheep" => "baa"
(Tuples and Sets and other container types work similarly to how you would expect in Python, so we won’t cover them explicitly)
Custom Composite Types#
You can also create composite types, struct
s - here you need to specify the types of their sub-fields. As with many Julia constructs, a struct definition starts with a keyword, struct
, and ends with end
:
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)