Introduction to Julia

Julia
Introduction
A brief introduction to Julia, its installation, and how to use it.
Published

April 23, 2025

Installation

Linux and macOS:

curl -fsSL https://install.julialang.org | sh

Windows

winget install julia -s msstore

How to use Julia

there are several ways to use Julia, including the REPL, Jupyter notebooks, and various IDEs like VS Code.

REPL

The Julia REPL (Read-Eval-Print Loop) is an interactive command-line interface that allows you to execute Julia commands and scripts. To start the Julia REPL, simply type julia in your terminal after installation. You can then execute Julia commands interactively.

$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.11.5 (2025-04-14)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> 5 * 7
35

julia> 

.jl files

You can write Julia code in .jl files and run them from the REPL or command line. For example, create a file named hello.jl:

println("Hello, World!")
$ julia hello.jl
Hello, World!

Jupyter Notebooks

Jupyter notebooks allow you to write and execute Julia code in an interactive environment. You can install the Julia kernel for Jupyter by running:

using Pkg
Pkg.add("IJulia")

Hello World

println("Hello, World!") # in just one line
let # create a local scope
  var::String = "Hello, World!" # assigning to a variable
  @show var # using @show macro
  display(var) # using display function
end
Hello, World!
var = "Hello, World!"
"Hello, World!"

Find Primes

Let’s write a simple function to check if a number is prime:

let
  # Define a function to check if a number is prime
  function is_prime(n::Int) ::Bool
    # Check if n ∈ ℕ
    @assert(n  0)
    # m is the last possible divider
    m::Int = floor(√n)
    # check any possible dividers
    for i  2:m
      # check if n is divisable by i
      if is_prime(i) && mod(n,i) == 0
        # return false if n is divisable by i
        return false
      end
    end
    # return true after checking that n was not divisable by any i ∈ 2:m
    return true
  end

  @show is_prime(6)
  @show is_prime(7)
  @show is_prime(97)
end
# return nothing to avoid printing the last result
nothing 
is_prime(6) = false
is_prime(7) = true
is_prime(97) = true

now let’s write a simple program to find prime numbers up to a given limit.

let
  function find_all_primes_till(n::Int) ::Vector{Int}
    # check if n ∈ ℕ
    @assert(n  0)
    # init the vector 
    primes::Vector{Int} = []
    # check all numbers till n
    for i  2:n
      # check if the it is divisable by any prime lower prime
      isDivisable::Bool = false
      m::Int = floor(√i)
      for p  primes
        if p > m
          break
        elseif mod(i,p) == 0
          isDivisable = true
          break
        end
      end
      if !isDivisable
        push!(primes, i)
      end
    end
    return primes
  end

  @show find_all_primes_till(100)
end; nothing # return nothing inside the cell
find_all_primes_till(100) = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

Libraries

Using Libraries in Julia is super easy

Plotting

first you need to install the pachage for plotting

import Pkg # import the pachage manager
Pkg.add("Plots") # install the package for plotting

then you can use the package:

let
  # importing the Plots package
  import Plots 

  # create a range of numbers between -4 and 4
  x = -4:0.1:4
  # @show is a macro that prints the name and the value of a variable
  @show typeof(x) # this will show that x is a range
  @show x # this will print x

  # create a vector of sine values for the range of numbers
  y = sin.(x) # note the . operator for element-wise operation
  @show typeof(y) # this will show that y is a vector
  @show y[1:3] # this will print the first three values of y

  # plot the sine function
  plot = Plots.plot(x, y, label="sin(x)", title="Sine Function", xlabel="x", 
      ylabel="sin(x)", linewidth=3)
  @show typeof(plot) # this will show that plot is a Plots.Plot object

  display(plot) # display the plot in the notebook or Julia REPL
end
typeof(x) = StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}
x = -4.0:0.1:4.0
typeof(y) = Vector{Float64}
y[1:3] = [0.7568024953079282, 0.6877661591839738, 0.6118578909427189]
typeof(plot) = Plots.Plot{Plots.GRBackend}

Structs

mutable vs. immutable structs

Julia has two types of Datatypes: mutable and immutable. Mutable structs can be modified after creation, while immutable structs cannot.

let
  # mutable structs can be modified
  mutable struct Vec2
    x::Float64
    y::Float64
  end

  # immutable structs cannot be modified
  struct Point2
    x::Float64
    y::Float64
  end

  # creating an instance of Vec2
  vec = Vec2(1.0, 2.0)
  # creating an instance of Point2
  point = Point2(3.0, 4.0)

  # mutable structs can be modified within functions, but can not be reassigned!
  # immutable structs are not allowed to be modified! That will throw an error!
  # as a convetion ! is used to indicate that the function modifies its argument
  function add1y!(v)::Nothing
    v.y += 1.0
    v = Vec2(0, 0) # this reassignment will not affect the original instance
    return nothing
  end

  # all base types are mutable, but reassigning them will not affect the original
  add1(x::Integer)::Integer = x += 1

  x = 55
  y = add1(x)
  @show x # this will show that x is still 55
  @show y # this will show that y is 56
  add1y!(vec) # this will modify the y value of v
  @show vec # this will show the modified Vec2 instance
  try 
    add1y!(point) # this will throw an error because Point2 is immutable
  catch e
    @show e # this will show the error message
  end
end; nothing # this is just to suppress the output of the cell
x = 55
y = 56
vec = Vec2(1.0, 3.0)
e = ErrorException("setfield!: immutable struct of type Point2 cannot be changed")

Generic structs

You can create generic structs in Julia that can hold any type of data. This is useful for creating data structures that can work with different types.

Example: Rational Number

let 
  # creat a rational struct that is a subtype of Real
  mutable struct Rational{T <: Integer} <: Real
    num::T
    den::T
    # constructor
    function Rational(num::T, den::T) where T <: Integer
      if den == 0
          throw(ArgumentError("Denominator cannot be zero"))
      end
      new{T}(num, den)
    end
  end

  # define a conversion method to convert Rational to Float64
  Base.convert(::Type{Float64}, r::Rational) = 
      Float64(r.num) / Float64(r.den)

  a = Rational(1, 2) # create an instance of Rational
  @show a # this will show the Rational instance
  b::Float64 = a # convert Rational to Float64
  @show b # this will show the converted Float64 value
end; nothing # display nothing
a = Rational{Int64}(1, 2)
b = 0.5

Example: StaticArray

let
  # create a static vector type that is a subtype of AbstractVector
  mutable struct StaticVector{T <: Any, N} <: AbstractVector{T}
    data::Vector{T} # store data in a vector
    # constructor to ensure the length matches N
    function StaticVector{T,N}(data::Vector{T}) where {T,N}
      length(data)==N ||
        throw(ArgumentError("Data length must be $N, got $(length(data))"))
      new(data)
    end
  end

  # implement the AbstractVector interface
  # length of the static vector is N
  Base.length(::StaticVector{T,N}) where {T,N} = N
  # indexing
  Base.getindex(sv::StaticVector{T,N}, i::Int) where {T,N} = sv.data[i]
  # setting values at a specific index
  Base.setindex!(sv::StaticVector{T,N}, v, i::Int) where {T,N} = (sv.data[i]=v)
  # defining the index style for StaticVector
  Base.IndexStyle(::Type{<:StaticVector}) = IndexStyle(Vector)
  # size of the static vector is (N,)
  Base.size(::StaticVector{T,N}) where {T,N} = (N,)
  # outer constructor to infer N from data
  StaticVector(data::Vector{T}) where T = StaticVector{T,length(data)}(data)

  # usage
  staticVector = StaticVector([1, 2, 3])
  @show staticVector
  println(staticVector[3])
end; nothing # display nothing
staticVector = [1, 2, 3]
3