TLDR: Use juliaup
Head to the Julia downloads page.
curl -fsSL https://install.julialang.org | sh
Warning: Do not install Julia using your system package manager or other package managers like Homebrew. Those versions are a frequent source of bug reports.
juliaup is the Julia version manager, which we use to manage different versions of Julia, download updates and more:
juliaup status
juliaup list
For now we are going to ensure you have the current release version of Julia using the following commands:
juliaup add release
juliaup default release
(This was probably already done on your first installation but we all want to be on the same page).
The Julia REPL is the primary way many users interact with Julia:
julia
While many languages like Python ship with a REPL, Julia's REPL is uniquely powerful and fully-featured.
TLDR: The Julia REPL has 4 default modes: Julia, package (]), help (?) and shell (;).
The Read-Eval-Print Loop (or REPL) is the most basic way to interact with Julia, check out its documentation for details.
You can start a REPL by typing julia into a terminal, or by clicking on the Julia application in your computer.
It will allow you to play around with arbitrary Julia code:
julia> a, b = 1, 2;
julia> a + b
3
This is the standard (Julia) mode of the REPL, but there are three other modes you need to know.
Each mode is entered by typing a specific character after the julia> prompt.
Once you're in a non-Julia mode, you stay there for every command you run.
To exit it, hit backspace after the prompt and you'll get the julia> prompt back.
?)By pressing ? you can obtain information and metadata about Julia objects (functions, types, etc.) or unicode symbols.
The query fetches the docstring of the object, which explains how to use it.
help?> println
println([io::IO], xs...)
Print (using ) xs to io followed by a newline. If io is not supplied, prints to the default output stream .
See also to add colors etc.
julia> println("Hello, world")
Hello, world
julia> io = IOBuffer();
julia> println(io, "Hello", ',', " world.")
julia> String(take!(io))
"Hello, world.\n"
If you don't know the exact name you are looking for, type a word surrounded by quotes to see in which docstrings it pops up.
])By pressing ] you access Pkg.jl, Julia's integrated package manager, whose documentation is an absolute must-read.
Pkg.jl allows you to:
]activate different local, shared or temporary environments;]instantiate them by downloading the necessary packages;]add, ]update (or ]up) and ]remove (or ]rm) packages;]status (or ]st) of your current environment.As an illustration, we download the package Example.jl inside our current environment:
(starting) pkg> add Example
β Warning: The Pkg REPL mode is intended for interactive use only, and should not be used from scripts. It is recommended to use the functional API instead.
β @ Pkg.REPLMode /opt/hostedtoolcache/julia/1.12.6/x64/share/julia/stdlib/v1.12/Pkg/src/REPLMode/REPLMode.jl:398
Resolving package versions...
Installed Example β v0.5.5
Updating `~/work/QNumericsCrashCourse/QNumericsCrashCourse/starting/Project.toml`
[7876af07] + Example v0.5.5
Updating `~/work/QNumericsCrashCourse/QNumericsCrashCourse/starting/Manifest.toml`
[7876af07] + Example v0.5.5
Precompiling packages...
338.1 ms β Example
1 dependency successfully precompiled in 0 seconds
(starting) pkg> status
Status `~/work/QNumericsCrashCourse/QNumericsCrashCourse/starting/Project.toml`
[7876af07] Example v0.5.5
Note that the same keywords are also available in Julia mode:
julia> using Pkg
julia> Pkg.rm("Example")
Updating `~/work/QNumericsCrashCourse/QNumericsCrashCourse/starting/Project.toml`
[7876af07] - Example v0.5.5
Updating `~/work/QNumericsCrashCourse/QNumericsCrashCourse/starting/Manifest.toml`
[7876af07] - Example v0.5.5
Info We haven't cleaned this depot up for a bit, running Pkg.gc()...
Active manifest files: 4 found
Active artifact files: 4 found
Active scratchspaces: 0 found
Deleted no artifacts, repos, packages or scratchspaces
The package mode itself also has a help mode, accessed with ?, in case you're lost among all these new keywords.
;)By pressing ; you enter a terminal, where you can execute any command you want.
Here's an example for Unix systems:
shell> ls ./writing
ls: cannot access './writing': No such file or directory
julia> 1 + 2
julia> 5 / 10
julia> sqrt(100)
Julia also has easy access to special Unicode characters, which includes a lot of common LaTeX math symbols:
julia> β2
You can access these symbols and many more wwith autocomplete:
julia> Ξ£ # type \Sigma and press Tab
Variables are assigned with the = sign. Variable names can be almost arbitrary Unicode:
julia> Ξ£ = +
+
julia> Ξ£(1,2,3)
6
Julia variables are essentially just names, they can be re-bound to arbitrary values like functions, types, numbers, etc.
julia> Ξ£ = 3
3
julia> Ξ£ + Ξ£
6
julia> π₯±, π, π€£ = 3.6, false, true
(3.6, false, true)
julia> π₯± + π + π€£
4.6
You can look up how to type any strange characters by pasting them into the help REPL:
help?> π
"π" can be typed by \:face_with_rolling_eyes:<tab>
Strings are concatenated with * (not +), repeated with ^, and support interpolation with $:
julia> foo = "The eye rolling emoji has value $(π)"
The eye rolling emoji has value false
julia> bar = " which is equal to $(3 - 2 > 10)"
which is equal to false
julia> foo * bar
The eye rolling emoji has value false which is equal to false
julia> "ha"^3
hahaha
The standard library ships with all the string-mangling helpers you'd expect:
julia> s = " Hello, Julia World! "
Hello, Julia World!
julia> length(s)
23
julia> uppercase(s)
HELLO, JULIA WORLD!
julia> lowercase(s)
hello, julia world!
julia> strip(s) # trim whitespace
Hello, Julia World!
julia> strip("--hi--", '-') # trim a particular character
hi
julia> startswith(s, " Hello")
true
julia> endswith(strip(s), "!")
true
julia> occursin("Julia", s)
true
julia> replace(s, "Julia" => "Python")
Hello, Python World!
julia> split("a,b,,c", ",") # split on a delimiter
SubString{String}["a", "b", "", "c"]
julia> join(["x", "y", "z"], "-") # inverse of split
x-y-z
julia> # `string` is the generic stringifier β accepts anything `show`-able
julia> string(1, " + ", 2, " = ", 1 + 2)
1 + 2 = 3
You've already seen println in passing. Here are the printing tools you'll reach for most often:
julia> print("no newline... ") # write to stdout, no newline
no newline...
julia> println("and now a newline") # newline at the end
and now a newline
julia> # Interpolation is the most common way to format output
julia> x, y = 3, 4
(3, 4)
julia> println("$x + $y = $(x + y)")
3 + 4 = 7
julia> # `@show` is invaluable for quick debugging: it prints `expr = value`
julia> @show x + y;
x + y = 7
julia> # `printstyled` lets you color or emphasize text in the terminal
julia> printstyled("careful!\n"; color=:yellow, bold=true)
careful!
There are two slightly different "render this value" functions:
print(x) produces the for-humans form β e.g. print("hi") writes hishow(x) produces the for-Julia form, the one you could paste back into the REPL β e.g. show("hi") writes "hi"julia> print("hello"); println()
hello
julia> show("hello"); println()
"hello"
| Op | Notes |
|---|---|
+, -, * |
the usual |
/ |
division, always returns a float |
Γ· (\div) |
integer (truncating) division |
% or rem |
remainder |
^ |
exponentiation, 2^10 not 2**10 |
==, != (or β ) |
(in)equality |
<, >, <= (β€), >= (β₯) |
comparisons |
=== |
identity, same object in memory |
julia> x = 0
0
julia> x += Ο
3.141592653589793
julia> x -= 2
1.1415926535897931
julia> x *= 3
3.4247779607693793
julia> x /= 4
0.8561944901923448
These operators desugar into:
julia> x = x + Ο # x += Ο
3.997787143782138
Julia has a nice juxtoposition syntax:
julia> x = 10
10
julia> 3x^2 + 4x + 5
345
true and false have their own type, Bool, you cannot just use the integers 0 or 1 in a conditional statement.
julia> typeof(true)
Bool
julia> true + true
2
if/elseif/else blocks are expressions, the last value is returned:
julia> x = 5
5
julia> y = if x > 0
"positive"
elseif x < 0
"negative"
else
"zero"
end
positive
Ternary form for short conditions:
julia> x > 0 ? "pos" : "nonpos"
pos
Short-circuit && and || are common in idiomatic Julia:
julia> x > 0 && println("yes")
yes
julia> x < 0 || println("nope")
nope
Long form, with explicit return:
julia> function add(x, y)
return x + y
end;
julia> add(1, 2)
3
return is optional β the last expression is returned automatically β but it's needed for early exits:
julia> function clamp_positive(x)
x < 0 && return 0
x
end;
julia> clamp_positive(-3), clamp_positive(4)
(0, 4)
Short (assignment) form:
julia> mul(x, y) = x * y;
julia> mul(3, 4)
12
Anonymous form, useful for one-off callbacks:
julia> (x -> x^2)(5)
25
You can bind an anonymous function to a name like any other value:
julia> f = (x, y) -> x + y^2;
julia> f(3, 4)
19
TLDR: Every value has a specific concrete type.
We're going to talk about types more later but for now:
julia> typeof(3)
Int64
julia> x = 3
3
julia> typeof(x)
Int64
julia> typeof(1.5)
Float64
julia> typeof(3 + 4im)
Complex{Int64}
julia> typeof("string cheese!")
String
julia> typeof((fooplus(x, y) = x + y))
ERROR: syntax: invalid keyword argument name "fooplus(x, y)" around string:1
Stacktrace:
julia> mytuple = ("Purple", 3.14159, "People", typeof, "Eater")
("Purple", 3.14159, "People", typeof, "Eater")
julia> mytuple[1] # Julia is 1-based!!! (Kinda)
Purple
julia> mytuple[begin]
Purple
julia> mytuple[end]
Eater
julia> typeof(mytuple)
Tuple{String, Float64, String, typeof(typeof), String}
julia> a, b, c = mytuple # unpack a tuple
("Purple", 3.14159, "People", typeof, "Eater")
julia> a, b... = mytuple # unpack the remainder into b
("Purple", 3.14159, "People", typeof, "Eater")
julia> for e in mytuple
println(e)
end
Purple
3.14159
People
typeof
Eater
Tuples are covariant in their field types, so Tuple{Int, Float64} <: Tuple{Real, Real}.
Like tuples but with named fields:
julia> mynt = (name="Purple", value=3.14159, fn=typeof)
(name = "Purple", value = 3.14159, fn = typeof)
julia> mynt.name # access by name
Purple
julia> mynt[1] # or by position
Purple
julia> mynt[end]
typeof
julia> typeof(mynt)
@NamedTuple{name::String, value::Float64, fn::typeof(typeof)}
julia> a, b, c = mynt # unpack positionally
(name = "Purple", value = 3.14159, fn = typeof)
julia> for v in mynt
println(v)
end
Purple
3.14159
typeof
Named tuples are also covariant.
Vectors use the [ ] literal:
julia> v = [1, 2, 3]
[1, 2, 3]
julia> v[1]
1
julia> v[begin], v[end]
(1, 3)
julia> v[1] = 99; v
[99, 2, 3]
julia> push!(v, 4); v
[99, 2, 3, 4]
julia> typeof(v) # Vector{Int64}
Vector{Int64}
The literal infers T from the contents β mix types and you fall back to Any:
julia> typeof([1, "two", 3.0])
Vector{Any}
Ranges are lazy iterables; collect materializes them as a Vector:
julia> r = 1:5
1:5
julia> r[1], r[end]
(1, 5)
julia> collect(r)
[1, 2, 3, 4, 5]
julia> 0:0.25:1
0.0:0.25:1.0
julia> typeof(r)
UnitRange{Int64}
julia> d = Dict("a" => 1, "b" => 2)
Dict("b" => 2, "a" => 1)
julia> d["a"]
1
julia> d["c"] = 3; d
Dict("c" => 3, "b" => 2, "a" => 1)
julia> haskey(d, "a")
true
julia> for (k, v) in d
println("$k => $v")
end
c => 3
b => 2
a => 1
julia> typeof(d) # Dict{String, Int64}
Dict{String, Int64}
Mixed key/value types collapse to Any:
julia> typeof(Dict(:x => 1, "y" => 2.0))
Dict{Any, Real}
for iterates over anything iterable (ranges, vectors, ...):
julia> for i in 1:3
println(i)
end
1
2
3
in can also be written β (\in<tab>):
julia> for i β 1:4
println("I love the number $i")
end
I love the number 1
I love the number 2
I love the number 3
I love the number 4
Warning: Unfortunately for various reasons plain scalar numbers are iterable. So watch out!
while runs until the condition fails:
julia> n = 3
3
julia> while n > 0
println(n)
n -= 1
end
3
2
1
The Vectors section above showed push! and the literal [ ] form. Here are the operations that show up in almost every script:
Mutating (the trailing ! is convention for "modifies its argument"):
julia> v = [3, 1, 4, 1, 5]
[3, 1, 4, 1, 5]
julia> push!(v, 9) # add to the end
[3, 1, 4, 1, 5, 9]
julia> pop!(v) # remove and return the last element
9
julia> pushfirst!(v, 0) # add to the front
[0, 3, 1, 4, 1, 5]
julia> popfirst!(v)
0
julia> append!(v, [2, 6]) # extend with another collection
[3, 1, 4, 1, 5, 2, 6]
julia> v
[3, 1, 4, 1, 5, 2, 6]
Aggregates β these work on anything iterable, not just vectors:
julia> length(v), sum(v), prod(v)
(7, 22, 720)
julia> maximum(v), minimum(v), extrema(v)
(6, 1, (1, 6))
julia> sum(1:100) # works on ranges too
5050
julia> sum(x^2 for x in 1:10) # generator: no intermediate vector
385
Slicing uses ranges β including the begin/end keywords:
julia> v = [10, 20, 30, 40, 50, 60]
[10, 20, 30, 40, 50, 60]
julia> v[2:4] # elements 2..4
[20, 30, 40]
julia> v[1:2:end] # every other element
[10, 30, 50]
julia> v[end-2:end] # last three
[40, 50, 60]
Searching and predicates β these take a function (or a Fix2 like >(3)):
julia> v = [3, 1, 4, 1, 5, 9, 2, 6]
[3, 1, 4, 1, 5, 9, 2, 6]
julia> findfirst(==(5), v) # index of the first 5
5
julia> findall(>(3), v) # all indices where the element is > 3
[3, 5, 6, 8]
julia> any(iseven, v)
true
julia> all(>(0), v)
true
julia> count(iseven, v)
3
Transformations:
julia> map(x -> x^2, v)
[9, 1, 16, 1, 25, 81, 4, 36]
julia> filter(iseven, v)
[4, 2, 6]
julia> sort(v)
[1, 1, 2, 3, 4, 5, 6, 9]
julia> sort(v; rev=true)
[9, 6, 5, 4, 3, 2, 1, 1]
julia> reverse(v)
[6, 2, 9, 5, 1, 4, 1, 3]
Comprehensions are a compact way to build vectors (or matrices, see below):
julia> [x^2 for x in 1:5]
[1, 4, 9, 16, 25]
julia> [x for x in 1:10 if iseven(x)] # with filter
[2, 4, 6, 8, 10]
julia> [(i, j) for i in 1:3, j in 1:3] # nested β matrix
[(1, 1) (1, 2) (1, 3); (2, 1) (2, 2) (2, 3); (3, 1) (3, 2) (3, 3)]
A matrix literal uses spaces between columns and ; between rows:
julia> A = [1 2 3
4 5 6
7 8 10]
[1 2 3; 4 5 6; 7 8 10]
julia> size(A) # (rows, cols)
(3, 3)
julia> size(A, 1) # just the row count
3
julia> ndims(A), eltype(A)
(2, Int64)
Indexing extends from vectors with a second axis. : means "all of this dimension":
julia> A[1, 2] # row 1, column 2
2
julia> A[:, 2] # all of column 2
[2, 5, 8]
julia> A[1, :] # all of row 1
[1, 2, 3]
julia> A[1:2, 2:3] # 2Γ2 submatrix
[2 3; 5 6]
Constructors you'll use constantly:
julia> zeros(2, 3)
[0.0 0.0 0.0; 0.0 0.0 0.0]
julia> ones(3, 3)
[1.0 1.0 1.0; 1.0 1.0 1.0; 1.0 1.0 1.0]
julia> rand(2, 4) # uniform on [0, 1)
[0.6549915943444159 0.05068523046329321 0.9213027318090715 0.2022613451070443; 0.7495060814279565 0.3687752906197864 0.840510284153138 0.9593642228247093]
julia> randn(2, 4) # standard normal
[-0.4415523735340024 -0.5829655980312182 0.6941926594750597 0.08000753528839065; -0.3017402533636737 0.926296672500494 0.852935060872784 -0.510954439620447]
Linear algebra is built in. * is real matrix multiplication, ' is the (conjugate) transpose, and \ solves linear systems:
julia> v = [1, 2, 3]
[1, 2, 3]
julia> A * v # matrixβvector
[14, 32, 53]
julia> A * A # matrixβmatrix
[30 36 45; 66 81 102; 109 134 169]
julia> A' # transpose
[1 4 7; 2 5 8; 3 6 10]
julia> A \ [1, 2, 3] # solve A x = b
[-0.33333333333333337, 0.6666666666666666, 3.172065784643303e-17]
sum, maximum, etc. accept dims to reduce along a particular axis:
julia> sum(A) # one scalar
46
julia> sum(A; dims=1) # 1Γ3, column sums
[12 15 19]
julia> sum(A; dims=2) # 3Γ1, row sums
[6; 15; 25;;]
If you put a . before an operator or after a function name, Julia applies it element by element. This is one of the most useful pieces of syntax in the language:
julia> v = [1, 2, 3, 4]
[1, 2, 3, 4]
julia> v .+ 10 # add 10 to every element
[11, 12, 13, 14]
julia> v .* v # elementwise square
[1, 4, 9, 16]
julia> v .^ 2
[1, 4, 9, 16]
julia> sin.(v) # apply sin to each element
[0.8414709848078965, 0.9092974268256817, 0.1411200080598672, -0.7568024953079282]
julia> sqrt.(1:5)
[1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.23606797749979]
It works for any function, including ones you just defined:
julia> greet(name) = "hi $name";
julia> greet.(["alice", "bob", "carol"])
["hi alice", "hi bob", "hi carol"]
The @. macro saves you from sprinkling dots everywhere β every operator and call in the expression becomes broadcasting:
julia> x = [1.0, 2.0, 3.0];
julia> @. x^2 + 2x + 1
[4.0, 9.0, 16.0]
Broadcasting also combines arrays of different shapes by aligning their dimensions. A row vector and a column vector give you the "outer" combination:
julia> row = [10 20 30] # 1Γ3
[10 20 30]
julia> col = [1, 2, 3] # length-3 column
[1, 2, 3]
julia> row .+ col # 3Γ3 outer sum
[11 21 31; 12 22 32; 13 23 33]
Time to combine functions, containers, broadcasting and printing into something useful. None of the functions below have type annotations β they're duck-typed, so they'll work on whatever you throw at them as long as the operations inside make sense.
julia> function fib(n)
a, b = 0, 1
for _ in 1:n
a, b = b, a + b
end
a
end;
julia> fib.(0:10) # broadcast over a range
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
The recursive one-liner is much slower but also much prettier:
julia> fib_rec(n) = n < 2 ? n : fib_rec(n - 1) + fib_rec(n - 2);
julia> fib_rec.(0:10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
julia> function quadratic(a, b, c)
s = sqrt(b^2 - 4a*c)
(-b + s) / (2a), (-b - s) / (2a)
end;
julia> quadratic(1, -3, 2) # roots of xΒ² β 3x + 2
(2.0, 1.0)
Because we never told Julia what a, b, c are, complex coefficients work for free β and that includes finding complex roots of a real polynomial if you pass a complex input:
julia> quadratic(1, 2 + 0im, 5) # complex roots of xΒ² + 2x + 5
(-1.0 + 2.0im, -1.0 - 2.0im)
julia> mean(xs) = sum(xs) / length(xs);
julia> variance(xs) = (m = mean(xs); sum((x - m)^2 for x in xs) / (length(xs) - 1));
julia> mean(1:10)
5.5
julia> mean([1.0, 2.0, 3.0])
2.0
julia> mean((4, 5, 6, 7)) # a tuple
5.5
julia> variance(1:10)
9.166666666666666
A classic example of a higher-order function β one that takes other functions as arguments:
julia> function newton(f, fβ², x0; tol=1e-10, maxiter=100)
x = x0
for _ in 1:maxiter
Ξ = f(x) / fβ²(x)
x -= Ξ
abs(Ξ) < tol && return x
end
x
end;
julia> # β2 as a root of xΒ² β 2
julia> newton(x -> x^2 - 2, x -> 2x, 1.0)
1.4142135623730951
julia> # Same code finds a complex root of xΒ² + 1
julia> newton(x -> x^2 + 1, x -> 2x, 1.0 + 1.0im)
8.463737877036721e-23 + 1.0im
A handful of broadcasts and a sum give us a Monte Carlo estimate of Ο by sampling points in the unit square and checking how many fall inside the unit quarter-disk:
julia> function estimate_Ο(n)
xs = rand(n)
ys = rand(n)
inside = sum(xs.^2 .+ ys.^2 .<= 1)
4 * inside / n
end;
julia> estimate_Ο(100_000)
3.1456
Strings, dictionaries, loops, and the get(d, k, default) pattern all in one place:
julia> function word_counts(text)
counts = Dict{String, Int}()
for word in split(text)
clean = lowercase(strip(word, ['.', ',', '!', '?', ';', ':']))
isempty(clean) && continue
counts[clean] = get(counts, clean, 0) + 1
end
counts
end;
julia> word_counts("To be, or not to be: that is the question. To live, or to die!")
Dict("question" => 1, "live" => 1, "or" => 2, "that" => 1, "not" => 1, "is" => 1, "the" => 1, "die" => 1, "to" => 4, "be" => 2)