First Steps
Getting started
The julia manual is excellent!
At this point we assume that you have Julia 1.9 installed, VSCode language extension ready, and installed the VSCode Julia plugin. There are some more recommended settings in VSCode which are not necessary, but helpful.
We further recommend to not use the small βplayβ button on the top right (which opens a new julia process everytime you change something), but rather open a new Julia repl (ctrl
+shift
+p
=> >Julia: Start Repl
) which you keep open as long as possible.
VSCode automatically loads the Revise.jl
package, which screens all your actively loaded packages/files and updates the methods instances whenever it detects a change. This is quite similar to %autoreload 2
in ipython. If you use VSCode, you dont need to think about it, if you prefer a command line, you should put Revise.jl in your startup.jl file.
Syntax differences Python/R/MatLab
Control Structures
Matlab User? Syntax will be very familiar.
R User? Forget about all the {}
brackets.
Python User? We donβt need no intendation, and we also have 1index.
= zeros(6)
myarray for k = 1:length(myarray)
if iseven(k)
= sum(myarray[1:k])
myarray[k] elseif k == 5
= myarray . 1
myarray else
= 5
myarray[k] end
end
 1

initialize a vector (check with
typeof(myArray)
)  2
 ControlStructure forloop. 1index!
 3

MatLab: Notice the
[
brackets to index Arrays!  4

Python/R:
.
always means elementwise  5

Python/R:
end
after each control sequence
Functions
function myfunction(a,b=123;keyword1="defaultkeyword")
if keyword1 == "defaultkeyword"
= a+b
c else
= a*b
c end
return c
end
methods(myfunction)
myfunction(0)
myfunction(1;keyword1 = "notdefault")
myfunction(0,5)
myfunction(0,5;keyword1 = "notdefault")
 1

everything before the
;
=> positional, after =>kwargs
 2

List all methods with that function name  returns two functions, due to the
b=123
optional positional argument
Terminology function vs. method: Methods are instantiations of an abstract function
= (x,y) > x+y
anonym anonym(3,4)
myshortfunction(x) = x^2
function mylongfunction(x)
return x^2
end
myfunction(args...;kwargs...) = myotherfunction(newarg,args...;kwargs...)
In the beginning there was nothing
nothing
 but also NaN
and also Missing
.
Each of those has a specific purpose, but most likely we will only need a = nothing
and b = NaN
.
Note that NaN
counts as a FloatNumber, whereas nothing & missing does not.
Excourse: splatting & slurping
Think of it as unpacking / collecting something
= [1,2,3]
a +(a)
+(a...)
 1

equivalent to
+(1,2,3)
elementwisefunction / broadcasting
Julia is very neat in regards of applying functions elementwise (also called broadcasting).
= [1,2,3,4]
a = sqrt(a)
b = sqrt.(a) c
 1

Error  there is no method defined for the
sqrt
of aVector
 2

the small
.
applies the function to all elements of the containera
 this works as βexpectedβ
Broadcasting is very powerful, as Julia can get a huge performance boost in chaining many operations, without requiring saving temporary arrays. For example:
= [1,2,3,4,5]
a = [6,7,8,9,10]
b
= (a.^2 .+ sqrt.(a) .+ log.(a.*b))./5 c
In many languages (Matlab, Python, R) you would need to do the following:
1. temp1 = a.*b
2. temp2 = log.(temp1)
3. temp3 = a.^2
4. temp4 = sqrt.(a)
5. temp5 = temp3 .+ temp4
6. temp6 = temp5 + temp2
7. output = temp6./5
Thus, we need to allocate ~7x the memory of the vector (not at the same time though).
In Julia, the elementwise code above rather translates to:
= similar(a)
c for k = 1:length(a)
= (a[k]^2 + sqrt(a[k]) + log(a[k]*b[k]))/5
c[k] end
 1

Function to initialize an
undef
array with the same size asa
The temp
memory we need at each iteration is simply c[k]
. And a nice sideeffect: By doing this, we get rid of any specialized βserializedβ function, e.g. to do sum, or + or whatever. Those are typically the inbuilt C
functions in Python/Matlab/R, that really speed up things. In Julia we do not need inbuilt functions for speed.
Linear Algebra
import LinearAlgebra
import LinearAlgebra: qr
using LinearAlgebra
 1

Requires to write
LinearAlgebra.QR(...)
to access a function  2

LinearAlgebra
is aBase
package, and always available
Julia typically recommends to use using PackageNames
. Namespace polution is not a problem, as the package manager will never silently overwrite an already existing method  it will always ask the user to specify in those cases (different to R: shows a warning, or Python: just goes on with life as if nothing happened)
= Matrix{Float64}(undef,11,22)
A = Array{Float64,2}(undef,22,33)
B qr(A*B)
 1

equivalent to
Array
, asMatrix
is a convenience typealias forArray
with 2 dimensions. Same thing forVector
.  2

the
2
of{Float64,2}
is not mandatory
Much more on Wednesday in the lecture LinearAlgebra
!
Styleconventions
variables  lowercase, lower_case 
Types,Modules  UpperCamelCase 
functions, macro  lowercase 
inplace / sideeffects  endwith!() ^{1} 
Task 1
Ok  lot of introduction, but I think you are ready for your first interactive task. Follow Task 1 here.
Julia Basics  II
Strings
= 'a'
character = "abc"
str 3] str[
 1

returns
c
characters
'a':'f'
collect('a':'f')
join('a':'f')
 1

a
StepRange
between characters  2

a
Array{Chars}
 3

a
String
concatenation
= "one"
a = "two"
b = a * b ab
 1

Indeed,
*
and not+
 as plus implies from algebra thata+b == b+a
which obviously is not true for string concatenation. Buta*b !== b*a
 at least for matrices.
substrings
= "long string"
str = SubString(str, 1, 4)
substr = findfirst("str",str) whereis_str
regexp
= "any WORD written in CAPITAL?"
str occursin(r"[AZ]+", str)
= match(r"[AZ]+",str) m
 1

Returns
true
. Note the smallr
before ther"regular expression"
 nifty!  2

Returns a
::RegexMatch
 access viam.match
&m.offset
(index)  orm.captures
/m.offsets
if you defined capturegroups
Interpolation
= 123
a = "this is a: $a; this 2*a: $(2*a)" str
Scopes
All things (excepts modules) are in local scope (in scripts)
= 0
a for k = 1:10
= 1
a end
a
 1
 a = 0!  in a script; but a = 1 in the REPL!
Variables are in global scope in the REPL for debugging convenience
Putting this code into a function automatically resolves this issue
function myfun()
= 0
a for k = 1:10
= 1
a end
areturn a
end
myfun()
 1
 returns 1 now in both REPL and include(βmyscript.jlβ)
explicit global / local
= 0
a global b
= 0
b for k = 1:10
local a
global b
= 1
a = 1
b end
a b
 1
 a = 0
 2
 b = 1
Modifying containers works in any case
= zeros(10)
a for k = 1:10
= k
a[k] end
a
 1

This works βcorrectlyβ in the
REPL
as well as in a script, because we modify the content ofa
, nota
itself
Types
Types play a super important role in Julia for several main reasons:
 The allow for specialization e.g.
+(a::Int64,b::Float64)
might have a different (faster?) implementation compared to+(a::Float64,b::Float64)
 They allow for generalization using
abstract
types  They act as containers, structuring your programs and tools
Everything in julia has a type! Check this out:
typeof(1)
typeof(1.0)
typeof(sum)
typeof([1])
typeof([(1,2),"5"])
We will discuss two types of types:
composite
typesabstract
types.
There is a third type, primitive type
 but we will practically never use them Not much to say at this level, they are types like Float64
. You could define your own one, e.g.
primitive type Float128 <: AbstractFloat 128 end
And there are two more, Singleton types
and Parametric types
 which (at least the latter), you might use at some point. But not in this tutorial.
composite types
You can think of these types as containers for your variables, which allows you for specialization.
struct SimulationResults
::Vector
parameters::Vector
resultsend
= SimulationResults([1,2,3],[5,6,7,8,9,10,NaN])
s
function print(s::SimulationResults)
println("The following simulation was run:")
println("Parameters: ",s.parameters)
println("And we got results!")
println("Results: ",s.results)
end
print(s)
function SimulationResults(parameters)
= run_simulation(parameters)
results return SimulationResults(parameters,results)
end
function run_simulation(x)
return cumsum(repeat(x,2))
end
= SimulationResults([1,2,3])
s print(s)
 1
 in case not all fields are directly defined, we can provide an outer constructor (there are also inner constructors, but we will not discuss them here)
once defined, a typedefinition in the global scope of the REPL cannot be redefined without restarting the julia REPL! This is annoying, there are some tricks arround it (e.g. defining the type in a module (see below), and then reloading the module)
Task 2
Follow Task 2 here
Julia Basics III
Modules
module MyStatsPackage
include("src/statistic_functions.jl")
export SimulationResults
export rse_tstat
end
using MyStatsPackage
 1

This makes the
SimulationResults
type immediately available after runningusing MyStatsPackage
. To use the other βinternalβ functions, one would useMyStatsPackage.rse_sum
.
import MyStatsPackage
rse_tstat(1:10)
MyStatsPackage.
import MyStatsPackage: rse_sum
rse_sum(1:10)
Macros
Macros allow to programmers to edit the actual code before it is run. We will pretty much just use them, without learning how they work.
@which cumsum
@which(cumsum)
= "123"
a @show a
Debugging
Debug messages
@debug "my debug message"
ENV["JULIA_DEBUG"] = Main
ENV["JULIA_DEBUG"] = MyPackage
Debugger proper:
In most cases, @run myFunction(1,2,3)
is sufficient.
Footnotes
A functionname ending with a
!
indicates that inplace operations will occur / sideeffects are possible. This is convention only, but in 99% of cases adoptedβ©οΈ