Lecture 38: Introduction to Julia#
Note
This lecture introduces the Julia Programming Language, focusing on its core design principles and practical use in scientific computing. We will cover installation, environment setup, basic syntax, and unique features such as multiple dispatch and type stability. By the end of this lecture, you should be comfortable writing and running simple Julia programs, managing packages, and understanding why Julia is positioned as a high-performance companion to R and Python in data science and engineering workflows.
Why Julia#
ulia combines the readability of high‑level languages with near‑C performance via JIT compilation and multiple dispatch. It is designed for numerical and scientific computing, making it a strong companion to R and Python in this course.
Install via juliaup
(recommended)#
juliaup
is the official version manager (Windows/macOS/Linux). It lets you install and switch versions safely.
Windows (PowerShell):
winget install julia -s msstore
juliaup add release # installs latest stable
juliaup default release # sets default
juliaup status # verify channels
macOS (Intel/Apple Silicon):
brew install juliaup
juliaup add release
juliaup default release
juliaup status
Linux (x86_64, aarch64):
curl -fsSL https://install.julialang.org | sh
# Then:
juliaup add release
juliaup default release
juliaup status
Tip
If you prefer standalone binaries, download from julialang.org/downloads and add julia
to PATH. But juliaup
is easier for managing versions.
Verifying#
julia --version
You should see something like julia version 1.x.y
.
Installing VS Code#
VS Code + Julia extension#
Install VS Code (Windows/macOS/Linux).
From Extensions panel, install Julia (by Julia Computing).
Point the extension to your Julia binary if needed:
VS Code → Settings → search
Julia: Executable Path
Example path (macOS via juliaup):
~/.juliaup/bin/julia
(Optional) Install CodeLLDB for debugging.
Quality-of-life extensions#
Inline Inlay Hints and Error Lens (already bundled in Julia ext.)
Jupyter if you prefer notebooks (
.ipynb
) in VS Code
REPL workflow#
Open a
.jl
file →Alt+J
thenAlt+O
(orCommand+J
on macOS) to open REPLSend line/selection with
Shift+Enter
Hello World!#
# Hello World in Julia
println("Hello, CE5540!")
Data Types in Julia#
Common scalar types:
Int64
,Float64
,Bool
Char
,String
Missing
,Nothing
ComplexF64
,Rational{Int}
Use typeof(x)
to inspect types.
# Character & String
x = 'J' # Char
s = "CE5540" # String
println("x = ", x, " :: ", typeof(x))
println("s = ", s, " :: ", typeof(s))
# Integers & Floats
i = 42 # Int64 (on 64-bit platforms)
r = 3.14 # Float64
println("i :: ", typeof(i), ", r :: ", typeof(r))
# Boolean
b = i > 40
println("b = ", b, " :: ", typeof(b))
# Complex & Rational
c = 2 + 3im
q = 3 // 7
println("c = ", c, " :: ", typeof(c))
println("q = ", q, " :: ", typeof(q))
# Missing / Nothing
m = missing
n = nothing
println("missing :: ", typeof(m), ", nothing :: ", typeof(n))
Data Structures in Julia#
Tuple (immutable), NamedTuple
Vector/Matrix (1D/2D
Array
), range objectsDict (hash map), Set
DataFrame (from
DataFrames.jl
)
Note: Many operations are not automatically vectorized; prefer broadcasting with the dot .
operator (e.g., sin.(x)
).
# Tuples and NamedTuples
t = (1, 2.0, "a")
nt = (a = 1, b = 2.0, c = "a")
println(t, " :: ", typeof(t))
println(nt, " :: ", typeof(nt))
# Arrays (Vector/Matrix) and ranges
v = [1, 2, 3, 4] # Vector{Int}
A = [1 2 3; 4 5 6] # 2×3 Matrix{Int}
r = 0:2:10 # range 0,2,4,6,8,10
println(v, " :: ", typeof(v))
println(A, " :: ", typeof(A))
println(collect(r), " :: ", typeof(r))
# Dict and Set
D = Dict("a" => 1, "b" => 2)
S = Set([1,2,2,3,3,3])
println(D, " :: ", typeof(D))
println(S, " :: ", typeof(S))
# Broadcasting with dot notation
x = [1.0, 4.0, 9.0]
y = sqrt.(x) # element-wise sqrt
println(y)
Control Flow#
Julia supports standard if/elseif/else
, for
, and while
constructs.
# If / elseif / else
x = 10
if x > 10
println("x > 10")
elseif x == 10
println("x == 10")
else
println("x < 10")
end
# For loop
s = 0
for k in 1:5
s += k
end
println("Sum 1..5 = ", s)
# While loop
i = 1
prod_ = 1
while i <= 5
prod_ *= i
i += 1
end
println("Product 1..5 = ", prod_)
Writing Functions in Julia (and Multiple Dispatch)#
Functions can be type-annotated.
# Factorial (iterative)
function factorial_iterative(n::Integer)
n < 0 && error("n must be non-negative")
result = one(n)
for k in 2:n
result *= k
end
return result
end
# Factorial (recursive)
function factorial_recursive(n::Integer)
n < 0 && error("n must be non-negative")
return n ≤ 1 ? one(n) : n * factorial_recursive(n-1)
end
println("5! iterative = ", factorial_iterative(5))
println("5! recursive = ", factorial_recursive(5))
Multiple Dispatch#
Julia selects methods at runtime based on the types of arguments.
# Multiple dispatch example
f(x::Int, y::Int) = x + y
f(x::Float64, y::Float64) = x + y
f(x, y) = string(x, y) # fallback (String concatenation)
println("f(2, 3) -> ", f(2,3), " :: ", typeof(f(2,3)))
println("f(2.0, 3.0) -> ", f(2.0,3.0), " :: ", typeof(f(2.0,3.0)))
println("f("a", 3) -> ", f("a", 3), " :: ", typeof(f("a",3)))
# Method lookup with @which (reveals the exact method that will be called)
# Try with our multiple-dispatch function f:
@which f(1, 2) # both Int → f(x::Int, y::Int)
@which f(1.0, 2.0) # both Float64 → f(x::Float64, y::Float64)
@which f(1, 2.0) # promotes to Float64 → f(x::Float64, y::Float64)
# Works for Base methods too
@which +(1, 2) # shows which method of + is used
@which *(1.0, 2) # multiplication method for Float64 × Int
Type Stability#
# Type stability matters for performance. A function is *type-stable*
# if the compiler can infer a concrete return type from the input types.
# Type-stable example
f1(v::Vector{Int})::Int = sum(v)
# Type-unstable example: operates on abstractly-typed inputs
f2(v::Vector{Any}) = sum(v)
# Construct two vectors
v_stable = [1, 2, 3, 4, 5] # Vector{Int}
v_unstable = Any[1, 2.0, 3, 4.0, 5] # Vector{Any} (mixed types)
# Inspect type inference
@code_warntype f1(v_stable) # should show a concrete return type (Int64 on 64-bit)
@code_warntype f2(v_unstable) # likely to show `Any`-typed intermediate/return
# Tips for stability:
# - Use concrete container element types (Vector{Float64}, Vector{Int}, etc.)
# - Avoid changing the type of a variable inside a function
# - Annotate return types where it clarifies intent (not always necessary)
Writing Fast Julia: Type Stability & Broadcasting#
Type stability: Ensure functions return a consistent type. Use
@code_warntype
in the REPL to diagnose instabilities.Avoid global state: Wrap code in functions; globals are slow.
Use broadcasting: Prefer
f.(x)
over manual loops when applying scalar functions element-wise.Preallocate: For large loops, preallocate arrays to avoid repeated memory allocations.
Creating a Julia Project#
# Julia environments and package management with Pkg
# Run in the Julia REPL or VS Code Julia REPL
import Pkg
# 1) Create/activate a new project (creates Project.toml and Manifest.toml)
Pkg.activate("ABM101") # local env in ./ABM101
# Alternatively: Pkg.generate("ABM101") # scaffolds a new package project
# 2) Add packages
Pkg.add([
"DataFrames",
"CSV",
"Plots",
"Distributions",
"StatsBase"
])
# 3) Check status / resolve
Pkg.status()
Pkg.resolve()
# 4) Pin (optional), update, and instantiate (recreate exact environment on new machine)
# Pkg.pin("DataFrames")
Pkg.update()
Pkg.instantiate()
# 5) Using packages in code (after activation)
using DataFrames, CSV, Plots, Distributions, StatsBase
# 6) Best practice for reproducibility:
# - Commit Project.toml & Manifest.toml to version control
# - Colleagues call `Pkg.activate("."); Pkg.instantiate()` to match your environment