💎 Computing with Julia

The Julia programming language is a high-level, high-performance languauge that is suitable for scientific computing applications. This website presents various aspects of the language along with code examples. All files for this website and the associated Julia code examples are available on GitHub.

Getting started

To get started, visit the main Julia website at https://julialang.org and follow their download and installation instructions. Julia is available for macOS, various flavors of Linux, FreeBSD, and Windows. After installation, read the sections below to learn more about the various features of the language.

Some of the code examples on this site use the julia> prompt to represent a line of executed code. This prompt is part of the Julia REPL which is accessed by entering the julia command in the terminal. As demonstrated in the code block shown below, julia> x = 2.5 assigns 2.5 to the x variable. The execution of this line displays a value of 2.5 in the REPL. Another example is adding two numbers such as 5 + 4.8 to get a result of 9.8. To exit the REPL enter exit() on a blank line or press Control-D on the keyboard.

$ julia

# Assign the value 2.5 to x
julia> x = 2.5
2.5

# Add two numbers
julia> 5 + 4.8
9.8

# Exit the Julia REPL with exit() or Control-D
julia> exit()

The Julia REPL is also used to install and manage Julia packages. To invoke the package manager environment, enter ] from within a REPL session. Use pkg> help to see an overview of the available commands. To exit the package manager, enter a backspace on an empty line or press Control-C on the keyboard.

$ julia

# Enter the package manager environment
julia> ]

# Add the Debugger.jl package
(@v1.6) pkg> add Debugger

# See installed Julia packages
(@v1.6) pkg> status
      Status `~/.julia/environments/v1.6/Project.toml`
  [31a5f54b] Debugger v0.6.8
  [682c06a0] JSON v0.21.1
  [2b0e0bc5] LanguageServer v4.0.0
  [91a5bcdd] Plots v1.16.5
  [cf896787] SymbolServer v7.0.0

# See list of available commands
(@v1.6) pkg> help

Comments

Single line and inline comments begin with # in Julia. Wrap multiple lines of text with #= =# for multiline comments.

# This is a single line comment

x = 9.4    # This is an inline comment

#=
This is a multiline comment.
Second line of the comments is here.
=#

Print

The print() function prints an undecorated text representation of an object. Similarly, the println() function prints text followed by a newline.

# Print text
julia> print("hello there")
hello there

# Print text followed by a newline
julia> println("hello there")
hello there

Strings

Strings can be represented by double quotes " " or by triple double quotes """ """. A character or group of characters are extracted from a string using an index or range. Combining strings can be accomplished with $, string, and *.

# String as a double quote
s1 = "This is a string"

# String as a triple double quote
s2 = """This is "another" string"""
"This is \"another\" string"

# Indexing a string
julia> s2[begin]
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)

julia> s2[end]
'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)

julia> s2[1]
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)

julia> s2[2:6]
"his i"

# Combining strings with $
s3 = "Hello"
s4 = "Julia"

julia> "$s3 $s4 programming!"
"Hello Julia programming!"

# Combining strings with string()
julia> s5 = string(s3, " ", s4, " programming!!")
"Hello Julia programming!!"

# Combining strings with *
julia> s6 = s3 * " " * s4 * " programming!!!"
"Hello Julia programming!!!"

Arrays

An array is a collection of items surrounded by square brackets [ ]. Arrays in Julia are 1-based therefore the first item in an array is at the 1-index.

# Array of strings representing fruits
fruits = ["orange", "melon", "apple", "lemon"]

# Get the first item
julia> fruits[1]
"orange"

# Get items two through three
julia> fruits[2:3]
2-element Vector{String}:
 "melon"
 "apple"

An item in an array can be replaced by assigning the index of that item to a new value.

# Array of numbers
numbers = [4.2, 8, 10, 3, 4]

# Replace the value of the first item
numbers[1] = 20

julia> numbers
5-element Vector{Float64}:
 20.0
  8.0
 10.0
  3.0
  4.0

Use the copy() function to copy an array to a new variable.

# Array of numbers
numbers2 = [4.2, 8, 10, 3, 4]

# Copy the array to a new variable
numbers3 = copy(numbers2)

# Replace value of the first item in original array
numbers2[1] = 100

julia> numbers2
5-element Vector{Float64}:
 100.0
   8.0
  10.0
   3.0
   4.0

# New array contains values from original array
julia> numbers3
5-element Vector{Float64}:
  4.2
  8.0
 10.0
  3.0
  4.0

Use an array comprehension to create an array from a range of numbers.

julia> z = [i * 2 for i in 1:5]
5-element Vector{Int64}:
  2
  4
  6
  8
 10

Julia’s arrays are stored in column-major order. Therefore, iterating over the columns first in a 2D array will execute quicker than iterating over the rows first. This is demonstrated in the example below where calcA() finishes in about 0.7 seconds while calcB() completes after 2.5 seconds for 10,000 iterations on a MacBook Pro.

# iterate over columns first then rows (faster)
function calcA(n)
    x = rand(n, n)
    z = zeros(n, n)

    for j in 1:n
        for i in 1:n
            xij = x[i, j]
            z[i, j] = xij^2
        end
    end

    return z
end

# iterate over rows first then columns (slower)
function calcB(n)
    x = rand(n, n)
    z = zeros(n, n)

    for i in 1:n
        for j in 1:n
            xij = x[i, j]
            z[i, j] = xij^2
        end
    end

    return z
end

julia> @time calcA(10_000);
  0.733511 seconds (4 allocations: 1.490 GiB, 6.50% gc time)

julia> @time calcB(10_000);
  2.527453 seconds (4 allocations: 1.490 GiB, 4.04% gc time)

Dictionaries

The Dict() constructor is used to create dictionaries in Julia. A dictionary is constructed using key value pairs separated with => or as tuples.

# Dictionary using key => value
contacts = Dict("Bart" => 14, "Marge" => 39, "Homer" => 44)

# Dictionary using (key, value)
contacts2 = Dict([("Lisa", 16), ("Krusty", 28)])

Tuples

A tuple is a fixed-length immutable container represented by parentheses ( ). Values in a tuple are accessed with indexing. A single item tuple must contain a comma.

# Tuple of strings
people = ("Marge", "Homer", "Bart", "Lisa")

julia> people[2]
"Homer"

# Tuple with one item
item = (2,)

julia> item[1]
2

A named tuple assigns names for each item in the tuple. Indexing a named tuple is accomplished with dot syntax or the regular indexing syntax.

# Named tuple
vehicle = (make="Ford", model="Bronco", year=2021)

julia> vehicle.make
"Ford"

julia> vehicle[1]
"Ford"

julia> vehicle.year
2021

Conditional statements

A conditional statement is written with the if-elseif-else syntax. The ternary operator ? : offers a concise syntax for a conditional expression.

x = 10
y = 17.2

# An if-else statement
if x > y
    println("the x > y")
else
    println("the x < y")
end

# An if-elseif-else statement
if x > y
    println("the x > y")
elseif x < y
    println("the x < y")
else
    println("x and y are equal")
end

# Ternary operator for an if-else statement
(x > y) ? println("the x > y") : println("the x < y")

# Can also write the above ternary statement as follows
println((x > y) ? "the x > y" : "the x < y")

Operators

The arithmetic operators shown below are available in Julia.

julia> 6 + 3
9

julia> 3 - 8
-5

julia> 4 * 5
20

julia> 14 / 5
2.8

julia> 14 % 5
4

julia> 9^2
81

Loops

Use a for-loop for repeated tasks and to iterate over arrays.

foods = ["melon", "apple", "berry", "orange"]

# Loop through the items in an array
for f in foods
    println("Food item is $f")
end

# Loop through a sequence of numbers
for x in 1:10
    println("x is $x")
end

Functions

In Julia, a function is an object where input arguments provide a return value.

# A typical function in Julia
function sayhello(x)
    println("Hello $x")
end

julia> sayhello("Batman")
Hello Batman

# A compact form of the above function
sayhello2(y) = println("Hello there $y")

julia> sayhello2("Superman")
Hello there Superman

# Another compact form of the above function
sayhello3 = z -> println("Hello again $z")

julia> sayhello3("Krusty")
Hello again Krusty

The built-in sort() function orders items in an array.

# Create a new sorted array
v1 = [4, 1, 9, 2, 5]
println("v1 = $v1")
println("sort(v1) = $(sort(v1))")
println("v1 = $v1")

# Sort an array (sort and modify the array)
v2 = [4, 1, 9, 2, 5]
println("v2 = $v2")
println("sort!(v2) = $(sort!(v2))")
println("v2 = $v2")

Modules

Modules help organize code that can be easily used in another program. The MyModule shown below is an example of a user defined module which includes functions from the funcA and funcB files. For this example the module and its associated files are contained in a folder called MyModule.

# Folder structure for the example module and associated files
MyModule
|- funcA.jl
|- funcB.jl
|- MyModule.jl
# MyModule.jl
module MyModule

include("funcA.jl")
include("funcB.jl")

export myfuncA

end
# funcA.jl
function myfuncA()
    println("Say hello from function A")
end

# funcB.jl
function myfuncB()
    println("Say hello from function B")
end

Two approaches to using a module are demonstrated below. The first approach imports the module with the import mechanism. This brings only the module name into the global scope. Functions associated with the module are accessed with dot notation.

# modules_import.jl

# Load the module file
include("MyModule/MyModule.jl")

# Import the module
import .MyModule

# Use the module functions
MyModule.myfuncA()
MyModule.myfuncB()
julia> modules_import.jl
Say hello from function A
Say hello from function B

The second approach loads the module with the using mechanism. This brings the module name and the exported elements of the module into the global scope.

# modules_using.jl

# Load the module
include("MyModule/MyModule.jl")

# Use the module and its exported function
using .MyModule

# Use the exported function
myfuncA()

# Use a function from the module
MyModule.myfuncB()
julia> modules_using.jl
Say hello from function A
Say hello from function B

JSON parsing

The JSON.jl package provides JSON parsing and printing capabilities in Julia. See the package’s documentation for installation and usage information. The example below parses the vehicles.json file which contains

[
    {
        "make": "Ford",
        "model": "Mustang",
        "year": 1979
    },
    {
        "make": "Ford",
        "model": "Explorer",
        "year": 2001
    }
]

and the fruits.json file which contains

{
    "apples": 8,
    "oranges": 2,
    "berries": 19,
    "melons": 4
}

This example uses the JSON package to parse the above JSON files.

import JSON

# Parse vehicles.json which contains an array of objects
julia> v = JSON.parsefile("vehicles.json")

julia> v[1]["model"]
"Mustang"

julia> v[1]["year"]
1979

# Parse fruits.json which contains several name:value pairs
julia> f = JSON.parsefile("fruits.json")

julia> f["berries"]
19

Debugging

One way to debug Julia code is to use the Infiltrator.jl package. As shown in the example file below, a breakpoint is set with the @infiltrate macro.

# debugging.jl

using Infiltrator

function calcs(x, y)
    a = x + y^2
    @infiltrate
    b = (a + 3) / x^2
    return b
end

z = calcs(2, 9)
println("z is $z")

To debug the file, open a Julia REPL then run the file in the REPL to enter the debugger mode infil>. View a list of local variables in the debugger with @locals or use ? to see the other available commands. Local variables can also be viewed and manipulated by just entering them into the debugger REPL.

julia> include("debugging.jl")

julia> include("debugging.jl")
Infiltrating calcs(x::Int64, y::Int64) at debugging.jl:10:

infil> @locals
- a::Int64 = 83
- y::Int64 = 9
- x::Int64 = 2

infil> x
2

Docstrings

Any string appearing directly above an object is interpreted as a docstring for that object. This documentation is treated as Markdown therefore code blocks and other Markdown supported features are supported. An example docstring for a function is shown below.

# example.jl

"""
    sayhello(x, y)

Print a hello greeting given a first `x` and last `y` name.

# Inputs

- `x`: the first name.
- `y`: the last name.

### Examples

```
julia> sayhello("Bart", "Simpson")
Saying hello to Bart Simpson.
```

"""
function sayhello(x, y)
    s = "Saying hello to $x $y."
    println(s)
end


a = "Bart"
b = "Simpson"
sayhello(a, b)

The docstring for the above function can be rendered in the Julia REPL by typing ? then the name of the function.

julia> include("example.jl")
julia> ?
help?> sayhello
search: sayhello

  sayhello(x, y)

  Print a hello greeting given a first x and last y name.

  Inputs
  ≡≡≡≡≡≡≡≡

      x: the first name.

      y: the last name.

  Examples
  ––––––––––

  julia> sayhello("Bart", "Simpson")
  Saying hello to Bart Simpson.

Testing

To demonstrate testing functions in Julia, two functions are defined below. A small program that uses the functions is also shown.

# adder.jl

function adder(x, y)
    a = x + y
    return a
end
# divider.jl

function divider(s, t)
    d = s / t
    return d
end
# main.jl

include("adder.jl")
include("divider.jl")

a = adder(2, 5)
println("a is $a")

d = divider(10, 3.5)
println("d is $d")

Testing the functions can be done with the Test package.

# tests.jl

using Test

include("adder.jl")
include("divider.jl")

@testset "All the tests" begin
    @test adder(2, 5) == 7
    @test divider(10, 3)  3.333333333
end

Running the tests from the command line produces the following results:

$ julia tests.jl

Test Summary: | Pass  Total
All the tests |    2      2

Gavin Wiggins © 2021