A Julia program is broken up into:
which organize code that can act on different data types.
Let's look at each of these in turn.
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:
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:
ModuleA.hello()
Hi from A
ModuleB.hello()
Hi from B
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 (like a Python indentation block, or a C/C++ curly brace) are enclosed in begin
and end
statements.
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):
@show y
y = 2
2
Conditional evaluation is done using and if ... elseif ... else ... end
block:
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
:
x < y ? println("x is less than y") : println("x is greater than, or equal to, y")
x is less than y
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.
(x < y) && println("hi there")
hi there
(x > y) && println("hi there")
false
Use for ... end
and while ... end
as loops. do ... end
is not a loop expression
for i in 1:10
print(i, ",")
end
1,2,3,4,5,6,7,8,9,10,
i = 0
while i < 10
print(i, ",")
i += 1
end
0,1,2,3,4,5,6,7,8,9,
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.
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
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
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
function fib_1(n)
if n <= 2
return 1
end
fib_1(n - 1) + fib_1(n - 2)
end
fib_1 (generic function with 1 method)
fib_1(32)
2178309
Functions can also be defined using a single line of code:
f(x) = 2x
f (generic function with 1 method)
f(3)
6
Let's try something more advanced: anonymous functions, and functions as inputs. This function take fn
as an input and returns fn(x)
:
function apply_fn(fn, x)
fn(x)
end
apply_fn (generic function with 1 method)
We can give it a function as an input:
function f(x)
x+1
end
apply_fn(f, 12)
13
We can also give it an anonymous function (like a C++ or Python lambda): using the ->
syntax:
apply_fn(x->x+1, 12)
13
The do <var> ... end
syntax is how your create a multi-statement anonymous function:
apply_fn(12) do x
x+1
end
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).
As you develop more sophisticated programs, it makes sense to organize the program into a Package.
To generate an empty package, run:
Pkg.generate("MyPkg")
Generating project MyPkg:
MyPkg/Project.toml
MyPkg/src/MyPkg.jl
Dict{String, Base.UUID} with 1 entry: "MyPkg" => UUID("7a423785-7114-4cc9-b33d-0f2a2494f990")
Which generates the following directory structure:
;tree
. └── MyPkg ├── Project.toml └── src └── MyPkg.jl 2 directories, 2 files
Which contains one hello world module:
module MyPkg
greet() = print("Hello World!")
end # module MyPkg