The Structure of a Julia Program¶

(Back to Overview)

A Julia program is broken up into:

  1. Modules
  2. Functions
  3. Statements

which organize code that can act on different data types.

Let's look at each of these in turn.

Modules¶

Modules encapsulate namespaces, and are similar to Python modules: https://docs.julialang.org/en/v1/manual/modules/

Created using the module keyword. They are frequently used to encapsulate source files into their own name-space:

module MyModule

include("my_function.jl")

export my_func

end

With the external source file my_function.jl:

function my_helper(...)
    ...
end

function my_func(...)
    ...
    my_helper(...)
    ...
end

This hides my_helper from all functions outside of MyModule.

Note that unlike Python, modules do not necessarily need to be in seperate files or folders. Later we will see how code in one file can add to a preexisting module.

Here is an example: Let's define two modules with the hello function:

In [1]:
module ModuleA
function hello()
    println("Hi from A")
end
end

module ModuleB
function hello()
    println("Hi from B")
end
end;

These can be called by using the <Module Name>.<Function Name> syntax:

In [2]:
ModuleA.hello()
Hi from A
In [3]:
ModuleB.hello()
Hi from B

Flow Control¶

Flow control in Julia is very similar to popular programming languages C++ or Python -- we'll briefly look at them here. For more details, refer to the documentation here: https://docs.julialang.org/en/v1/manual/control-flow/#man-tasks

The main difference to other programming languages is that nested statements are begin with the statement keyword and end using the end keyword.

Note that the do expression is different from other languages (more about this in the Functions section later.

Compound Expressions¶

Compound expressions (like a Python indentation block, or a C/C++ curly brace) are enclosed in begin and end statements.

In [4]:
z = begin
    x = 1
    y = 2 
    x + y
end

@show z;
z = 3

Note that begin and end blocks are not their own scope (so not quite like a C curly brace):

In [5]:
@show y
y = 2
Out[5]:
2

Conditional Evaluation¶

Conditional evaluation is done using and if ... elseif ... else ... end block:

In [6]:
x = 1
y = 2
if x < y
    println("x is less than y")
elseif x > y
    println("x is greater than y")
else
    println("x is equal to y")
end
x is less than y

Another popular conditional block is the terniary operator a ? b : c:

In [7]:
x < y ? println("x is less than y") : println("x is greater than, or equal to, y")
x is less than y

Short Circuit Evaluation¶

The && and || (logical and / or) evaluate only as much as is necessary to resolve the truth value of the expression. So they won't necessarily evaluate both sides, depending on the state of the first statement.

In [8]:
(x < y) && println("hi there")
hi there
In [9]:
(x > y) && println("hi there")
Out[9]:
false

Loops¶

Use for ... end and while ... end as loops. do ... end is not a loop expression

In [10]:
for i in 1:10
    print(i, ",")
end
1,2,3,4,5,6,7,8,9,10,
In [11]:
i = 0
while i < 10
    print(i, ",")
    i += 1
end
0,1,2,3,4,5,6,7,8,9,

Catching errors¶

The try ... catch <variable holder error> ... finally ... end block allows you to safely catch errors. The finally clause is useful to run code regardless of how the code being "try'ed" exists.

In [14]:
try
    sqrt(-1)
catch e
    if isa(e, DomainError)
        println("You (usually) can't take the square root of a negative number")
    end
finally
    println("This always executes")
end
You (usually) can't take the square root of a negative number
This always executes

Asynchronous programming¶

This is beyond the scope of this tutorial -- but it's still really useful stuff, so check this out: https://docs.julialang.org/en/v1/manual/asynchronous-programming/#man-asynchronous

Functions¶

The function keyword is used to declare a (multi-line) function. The last statement is automatically returned. Intermediate returns can be triggered using the return keyword

In [1]:
function fib_1(n)
    if n <= 2
        return 1
    end

    fib_1(n - 1) + fib_1(n - 2)
end
Out[1]:
fib_1 (generic function with 1 method)
In [2]:
fib_1(32)
Out[2]:
2178309

Functions can also be defined using a single line of code:

In [4]:
f(x) = 2x
Out[4]:
f (generic function with 1 method)
In [5]:
f(3)
Out[5]:
6

Let's try something more advanced: anonymous functions, and functions as inputs. This function take fn as an input and returns fn(x):

In [2]:
function apply_fn(fn, x)
    fn(x)
end
Out[2]:
apply_fn (generic function with 1 method)

We can give it a function as an input:

In [3]:
function f(x)
    x+1
end
apply_fn(f, 12)
Out[3]:
13

We can also give it an anonymous function (like a C++ or Python lambda): using the -> syntax:

In [4]:
apply_fn(x->x+1, 12)
Out[4]:
13

The do <var> ... end syntax is how your create a multi-statement anonymous function:

In [8]:
apply_fn(12) do x
    x+1
end
Out[8]:
13

The function signature of apply_fn is apply_fn(fn, x) (different x than in the do ... end block). do then replaces the first variable of apply_fn with the of the block (using the variable list after do to define anonymous function inputs).