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 1-index.
= 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
- Control-Structure for-loop. 1-index!
- 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 Float-Number, 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)
elementwise-function / 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
. Name-space 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 type-alias forArray
with 2 dimensions. Same thing forVector
. - 2
-
the
2
of{Float64,2}
is not mandatory
Much more on Wednesday in the lecture LinearAlgebra
!
Style-conventions
variables | lowercase, lower_case |
Types,Modules | UpperCamelCase |
functions, macro | lowercase |
inplace / side-effects | 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"[A-Z]+", str)
= match(r"[A-Z]+",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 capture-groups
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 type-definition in the global scope of the REPL cannot be re-defined 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 / side-effects are possible. This is convention only, but in 99% of cases adoptedβ©οΈ