"""
Multplies I by J by K
"""
function stupid_loop(I,J,K)
= 0.0
t for i=1:I
for j=1:J
for k = 1:K
+= 1.0
t end
end
end
return t
end
stupid_loop
Some useful links from QuantEcon:
Excellent resources at: julialang - checkout JuliaAcademy, it’s free
How I learnt: interpreted code is slow, so vectorize your coe.
"""
Multplies I by J by K
"""
function stupid_loop(I,J,K)
= 0.0
t for i=1:I
for j=1:J
for k = 1:K
+= 1.0
t end
end
end
return t
end
stupid_loop
@time stupid_loop(1000,1000,1000)
0.530100 seconds
1.0e9
@time [ stupid_loop(1000,1000,i) for i =1:10]
Code is translated to LLVM code then to instructions for the processor. Note that processor instructions are shorter than LLVM code.
@code_llvm stupid_loop(10,10,10)
; Function Signature: stupid_loop(Int64, Int64, Int64)
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:4 within `stupid_loop`
define double @julia_stupid_loop_6611(i64 signext %"I::Int64", i64 signext %"J::Int64", i64 signext %"K::Int64") #0 {
top:
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl within `stupid_loop`
%".I::Int64" = call i64 @llvm.smax.i64(i64 %"I::Int64", i64 0)
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:6 within `stupid_loop`
; ┌ @ range.jl:904 within `iterate`
; │┌ @ range.jl:681 within `isempty`
; ││┌ @ operators.jl:379 within `>`
; │││┌ @ int.jl:83 within `<`
%0 = icmp slt i64 %"I::Int64", 1
; └└└└
br i1 %0, label %L85, label %L16.preheader
L16.preheader: ; preds = %top
%".J::Int64" = call i64 @llvm.smax.i64(i64 %"J::Int64", i64 0)
%1 = icmp slt i64 %"J::Int64", 1
%".K::Int64" = call i64 @llvm.smax.i64(i64 %"K::Int64", i64 0)
%2 = icmp slt i64 %"K::Int64", 1
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:7 within `stupid_loop`
%or.cond = select i1 %1, i1 true, i1 %2
br i1 %or.cond, label %L85, label %L16.preheader49
L16.preheader49: ; preds = %L16.preheader
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:8 within `stupid_loop`
%3 = add nsw i64 %".K::Int64", -1
br label %L16
L16: ; preds = %L74.loopexit.split, %L16.preheader49
%value_phi3 = phi i64 [ %11, %L74.loopexit.split ], [ 1, %L16.preheader49 ]
%value_phi4 = phi double [ %.lcssa, %L74.loopexit.split ], [ 0.000000e+00, %L16.preheader49 ]
br label %L33
L33: ; preds = %L63.loopexit, %L16
%value_phi11 = phi double [ %.lcssa, %L63.loopexit ], [ %value_phi4, %L16 ]
%value_phi12 = phi i64 [ %10, %L63.loopexit ], [ 1, %L16 ]
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`
%xtraiter = and i64 %".K::Int64", 3
%4 = icmp ult i64 %3, 3
br i1 %4, label %L63.loopexit.unr-lcssa, label %L33.new
L33.new: ; preds = %L33
%unroll_iter = and i64 %".K::Int64", 9223372036854775804
br label %L50
L50: ; preds = %L50, %L33.new
%value_phi19 = phi double [ %value_phi11, %L33.new ], [ %8, %L50 ]
%niter = phi i64 [ 0, %L33.new ], [ %niter.next.3, %L50 ]
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`
; ┌ @ float.jl:491 within `+`
%5 = fadd double %value_phi19, 1.000000e+00
%6 = fadd double %5, 1.000000e+00
%7 = fadd double %6, 1.000000e+00
%8 = fadd double %7, 1.000000e+00
; └
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`
%niter.next.3 = add i64 %niter, 4
%niter.ncmp.3 = icmp eq i64 %niter.next.3, %unroll_iter
br i1 %niter.ncmp.3, label %L63.loopexit.unr-lcssa, label %L50
L63.loopexit.unr-lcssa: ; preds = %L50, %L33
%.lcssa.ph = phi double [ undef, %L33 ], [ %8, %L50 ]
%value_phi19.unr = phi double [ %value_phi11, %L33 ], [ %8, %L50 ]
%lcmp.mod.not = icmp eq i64 %xtraiter, 0
br i1 %lcmp.mod.not, label %L63.loopexit, label %L50.epil
L50.epil: ; preds = %L50.epil, %L63.loopexit.unr-lcssa
%value_phi19.epil = phi double [ %9, %L50.epil ], [ %value_phi19.unr, %L63.loopexit.unr-lcssa ]
%epil.iter = phi i64 [ %epil.iter.next, %L50.epil ], [ 0, %L63.loopexit.unr-lcssa ]
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`
; ┌ @ float.jl:491 within `+`
%9 = fadd double %value_phi19.epil, 1.000000e+00
; └
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`
%epil.iter.next = add i64 %epil.iter, 1
%epil.iter.cmp.not = icmp eq i64 %epil.iter.next, %xtraiter
br i1 %epil.iter.cmp.not, label %L63.loopexit, label %L50.epil
L63.loopexit: ; preds = %L50.epil, %L63.loopexit.unr-lcssa
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`
; ┌ @ float.jl:491 within `+`
%.lcssa = phi double [ %.lcssa.ph, %L63.loopexit.unr-lcssa ], [ %9, %L50.epil ]
; └
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:11 within `stupid_loop`
; ┌ @ range.jl:908 within `iterate`
; │┌ @ promotion.jl:639 within `==`
%.not.not37 = icmp eq i64 %value_phi12, %".J::Int64"
; │└
%10 = add nuw i64 %value_phi12, 1
; └
br i1 %.not.not37, label %L74.loopexit.split, label %L33
L74.loopexit.split: ; preds = %L63.loopexit
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:12 within `stupid_loop`
; ┌ @ range.jl:908 within `iterate`
; │┌ @ promotion.jl:639 within `==`
%.not.not38 = icmp eq i64 %value_phi3, %".I::Int64"
; │└
%11 = add nuw i64 %value_phi3, 1
; └
br i1 %.not.not38, label %L85, label %L16
L85: ; preds = %L74.loopexit.split, %L16.preheader, %top
%value_phi34 = phi double [ 0.000000e+00, %top ], [ 0.000000e+00, %L16.preheader ], [ %.lcssa, %L74.loopexit.split ]
; @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:13 within `stupid_loop`
ret double %value_phi34
}
@code_native stupid_loop(10,10,10)
.text
.file "stupid_loop"
.section .rodata.cst8,"aM",@progbits,8
.p2align 3, 0x0 # -- Begin function julia_stupid_loop_6783
.LCPI0_0:
.quad 0x3ff0000000000000 # double 1
.text
.globl julia_stupid_loop_6783
.p2align 4, 0x90
.type julia_stupid_loop_6783,@function
julia_stupid_loop_6783: # @julia_stupid_loop_6783
; Function Signature: stupid_loop(Int64, Int64, Int64)
; ┌ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:4 within `stupid_loop`
# %bb.0: # %top
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl within `stupid_loop`
#DEBUG_VALUE: stupid_loop:I <- $rdi
#DEBUG_VALUE: stupid_loop:J <- $rsi
#DEBUG_VALUE: stupid_loop:K <- $rdx
xor eax, eax
test rdi, rdi
vxorpd xmm0, xmm0, xmm0
cmovg rax, rdi
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:6 within `stupid_loop`
jle .LBB0_13
# %bb.1: # %L16.preheader
xor ecx, ecx
test rsi, rsi
cmovg rcx, rsi
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:7 within `stupid_loop`
jle .LBB0_13
# %bb.2: # %L16.preheader
test rdx, rdx
jle .LBB0_13
# %bb.3: # %L16.preheader49
push rbp
mov rsi, rdx
movabs r10, offset .LCPI0_0
vxorpd xmm0, xmm0, xmm0
mov r9d, 1
mov rbp, rsp
sar rsi, 63
vmovsd xmm1, qword ptr [r10] # xmm1 = mem[0],zero
andn rdx, rsi, rdx
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:8 within `stupid_loop`
mov r8d, edx
mov rdi, rdx
lea rsi, [rdx - 1]
and rdi, -4
and r8d, 3
pop rbp
jmp .LBB0_4
.p2align 4, 0x90
.LBB0_12: # %L74.loopexit.split
# in Loop: Header=BB0_4 Depth=1
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:12 within `stupid_loop`
; │┌ @ range.jl:908 within `iterate`
; ││┌ @ promotion.jl:639 within `==`
cmp r9, rax
; ││└
lea r9, [r9 + 1]
; │└
je .LBB0_13
.LBB0_4: # %L16
# =>This Loop Header: Depth=1
# Child Loop BB0_5 Depth 2
# Child Loop BB0_7 Depth 3
# Child Loop BB0_10 Depth 3
mov r10d, 1
jmp .LBB0_5
.p2align 4, 0x90
.LBB0_11: # %L63.loopexit
# in Loop: Header=BB0_5 Depth=2
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:11 within `stupid_loop`
; │┌ @ range.jl:908 within `iterate`
; ││┌ @ promotion.jl:639 within `==`
cmp r10, rcx
; ││└
lea r10, [r10 + 1]
; │└
je .LBB0_12
.LBB0_5: # %L33
# Parent Loop BB0_4 Depth=1
# => This Loop Header: Depth=2
# Child Loop BB0_7 Depth 3
# Child Loop BB0_10 Depth 3
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`
cmp rsi, 3
jb .LBB0_8
# %bb.6: # %L33.new
# in Loop: Header=BB0_5 Depth=2
mov r11, rdi
.p2align 4, 0x90
.LBB0_7: # %L50
# Parent Loop BB0_4 Depth=1
# Parent Loop BB0_5 Depth=2
# => This Inner Loop Header: Depth=3
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`
; │┌ @ float.jl:491 within `+`
vaddsd xmm0, xmm0, xmm1
; │└
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`
add r11, -4
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`
; │┌ @ float.jl:491 within `+`
vaddsd xmm0, xmm0, xmm1
vaddsd xmm0, xmm0, xmm1
vaddsd xmm0, xmm0, xmm1
; │└
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`
jne .LBB0_7
.LBB0_8: # %L63.loopexit.unr-lcssa
# in Loop: Header=BB0_5 Depth=2
test dl, 3
je .LBB0_11
# %bb.9: # %L50.epil.preheader
# in Loop: Header=BB0_5 Depth=2
mov r11, r8
.p2align 4, 0x90
.LBB0_10: # %L50.epil
# Parent Loop BB0_4 Depth=1
# Parent Loop BB0_5 Depth=2
# => This Inner Loop Header: Depth=3
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`
; │┌ @ float.jl:491 within `+`
vaddsd xmm0, xmm0, xmm1
; │└
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`
add r11, -1
jne .LBB0_10
jmp .LBB0_11
.LBB0_13: # %L85
; │ @ /home/pablo/Teaching/polytechnique/eco309/tutorials/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:13 within `stupid_loop`
ret
.Lfunc_end0:
.size julia_stupid_loop_6783, .Lfunc_end0-julia_stupid_loop_6783
; └
# -- End function
.type ".L+Core.Float64#6785",@object # @"+Core.Float64#6785"
.section .rodata,"a",@progbits
.p2align 3, 0x0
".L+Core.Float64#6785":
.quad ".L+Core.Float64#6785.jit"
.size ".L+Core.Float64#6785", 8
.set ".L+Core.Float64#6785.jit", 126257775752480
.size ".L+Core.Float64#6785.jit", 8
.section ".note.GNU-stack","",@progbits
Assignement operator is = (equality is ==, identity is ===)
# Assign the value 10 to the variable x
= 10 x
2 == 3
# Variable names can have Unicode characters
# To get ϵ in the REPL, type \epsilon<TAB>
= 1e-4 ϵ
Default semantic is pass-by-reference:
= [1,2,3,4]
a = a
b 1] = 10
a[ b
To work on a copy: copy
or deepcopy
= [1,2,3,4]
a = copy(a)
b 1]=10
a[ b
.== b a
=== b a
# for any object `typeof` returns the type
?typeof
typeof(a)
= 2 + 2 y
-y
0.34*23
3/4
# Scalar multiplication doesn't require *
3(4 - 2)
= 4
x 2x
typeof(x)
sizeof(a)
Equality
0 == 1
2 != 3
3 <= 4
Identity
= [34, 35]
a = [34, 35]
b = a c
=== a c
=== a b
Boolean operator
true && false
true || false
true !
# Strings are written using double quotes
= "This is a string" str
= 'k' # this is a character ch
# Strings can also contain Unicode characters
= "α is a string" fancy_str
# String interpolation using $
# The expression in parentheses is evaluated and the result is
# inserted into the string
= 2+2
a "2 + 2 = $(a)"
println("It took me $(a) iterations")
# String concatenation using *
"hello" * "world"
println("hello ", "world")
Julia has one-dimensional arrays. They are also called Vector.
= [1, 2] A
2-element Array{Int64,1}:
1
2
sizeof(A)
16
typeof(A) == Vector{Int64}
# vectors have one dimension: they are indexed by an integer
1] A[
1
2d arrays are also called matrices… and can be used for matrix multiplications.
= [0.1 0.2 0.3; 4 5 6] B
2×3 Array{Float64,2}:
0.1 0.2 0.3
4.0 5.0 6.0
*B' B
2×2 Array{Float64,2}:
0.14 3.2
3.2 77.0
Vectorized operations take a ., even comparisons:
.*B B
2×3 Array{Float64,2}:
0.01 0.04 0.09
16.0 25.0 36.0
.* B .< B B
2×3 BitArray{2}:
1 1 1
0 0 0
Elements are always accessed with square brackets:
1,2] B[
0.2
:,1] B[
2-element Array{Float64,1}:
0.1
4.0
:,1:end-1] B[
2×2 Array{Float64,2}:
0.1 0.2
4.0 5.0
Conditions
= -3
x if x < 0
println("x is negative")
elseif x > 0 # optional and unlimited
println("x is positive")
else # optional
println("x is zero")
end
x is negative
While
= 3
i while i > 0
println(i)
= i - 1
i end
3
2
1
For loops
# Iterate through ranges of numbers
for i = 1:3
println(i)
if i == 2
break
end
end
1
2
# Iterate through arrays
= ["Boston", "New York", "Philadelphia"]
cities for city in cities
println(city)
end
Boston
New York
Philadelphia
for t in zip(cities, states)
println(t)
end
("Boston", "MA")
("New York", "NY")
("Philadelphia", "PA")
# Iterate through arrays of tuples using zip
for (city, state) in zip(cities, states)
println("$city, $state")
end
Boston, MA
New York, NY
Philadelphia, PA
# Iterate through arrays and their indices using enumerate
for (i, city) in enumerate(cities)
println("City number $i is $city")
end
City number 1 is Boston
City number 2 is New York
City number 3 is Philadelphia
^2 for i=1:10] [i
10-element Array{Int64,1}:
1
4
9
16
25
36
49
64
81
100
^2 for i=1:10 if mod(i,2)==0] [i
5-element Array{Int64,1}:
4
16
36
64
100
Basic functions
function f(a,b,c)
= a + b +c
res return res
end
f (generic function with 1 method)
f(3,4,3)
10
Optional arguments
function f(a,b,c=1)
= a + b +c
res return res
end
f (generic function with 2 methods)
f(1,2,3)
6
f(1,2)
4
Keyword arguments
function g(a,b,c; operator=(+), add_one=false)
= operator(a, operator( b , c) )
res if add_one
+=1
res end
return res
end
g (generic function with 1 method)
g(1,2,3)
6
g(1.0,2.9,3.0; operator=(/))
1.0344827586206897
g(1.0,2.9,3.0; operator=(/), add_one=true)
2.0344827586206895
g(1.0,2.9,3.0; add_one=true, operator=(/), )
2.0344827586206895
One liners:
g(x,y) = x^2 + y^2
g (generic function with 2 methods)
Anonymous function:
= (x,y) -> x^2 + 1 + y fun
#12 (generic function with 1 method)
f(1,2,3; operator=fun)
10
A composite type is a collection of named fields that can be treated as a single value. They bear a passing resemblance to MATLAB structs.
All fields must be declared ahead of time. The double colon, ::
, constrains a field to contain values of a certain type. This is optional for any field.
struct Parameter_without_types
value
nameend
= Parameter_without_types(4.4, "no type at all") s
Parameter_without_types(4.4, "no type at all")
# Type definition
struct Parameter
::Float64
value::Function # Function is a type!
transformation::String
tex_label::String
descriptionend
Parameter(4,fun,"\\Gamma", "My parameter")
Parameter(4.0, var"#12#13"(), "\\Gamma", "My parameter")
When a type with
# Creating an instance of the Parameter type using the default
# constructor
= Parameter(0.9, identity, "\beta", "Discount rate") β
Parameter(0.9, identity, "\beta", "Discount rate")
β.value
0.9
Parameter(value, transformation, tex) = Parameter(value, transformation, tex, "no description")
Parameter
Parameter(0.34, identity, "\beta")
Parameter(0.34, identity, "\beta", "no description")
# constructor A
Parameter(value, tex) = Parameter(value, identity, tex, "no description")
Parameter
Parameter(0.1, "\beta")
Parameter(0.1, identity, "\beta", "no description")
# constructor B
Parameter(value, transformation) = Parameter(value, transformation, "notex", "no description")
Parameter
Parameter(0.1, "\beta")
MethodError: Cannot `convert` an object of type String to an object of type Function Closest candidates are: convert(::Type{T}, ::T) where T at essentials.jl:205 Stacktrace: [1] Parameter(value::Float64, transformation::String, tex_label::String, description::String) @ Main ./In[35]:3 [2] Parameter(value::Float64, transformation::String) @ Main ./In[50]:2 [3] top-level scope @ In[51]:1 [4] eval @ ./boot.jl:360 [inlined] [5] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String) @ Base ./loading.jl:1094
# here Julia doesn't know whether it should call constructor A or constructor B
# solution : give different signatures to the various constructors
Parameter(value, tex::String) = Parameter(value, identity, tex, "no description")
Parameter(value, transformation::Function) = Parameter(value, transformation, "notex", "no description")
Parameter
Parameter(0.5, "\beta")
Parameter(0.5, identity, "\beta", "no description")
Parameter(0.9, u->u^2)
Parameter(0.9, var"#14#15"(), "notex", "no description")
methods( Parameter )
# Alternative constructors end with an appeal to the default
# constructor
function Parameter(value::Float64, tex_label::String)
= identity
transformation = "No description available"
description return Parameter(value, transformation, tex_label, description)
end
= Parameter(0.5, "\alpha") α
Parameter(0.5, identity, "\alpha", "No description available")
Now the function Parameter
has two different methods
with different signatures:
methods(Parameter)
# Find the fields of an instance of a composite type
fieldnames(α)
α.tex_label
# Access a particular field using .
α.value
# Fields are modifiable and can be assigned to, like
# ordinary variables
= 0.75 α.value
by default structures in Julia are non-mutable
= 0.6 β.value
LoadError: setfield! immutable struct of type Parameter cannot be changed
setfield! immutable struct of type Parameter cannot be changed
Stacktrace:
[1] setproperty!(x::Parameter, f::Symbol, v::Float64)
@ Base ./Base.jl:34
[2] top-level scope
@ In[77]:1
[3] eval
@ ./boot.jl:360 [inlined]
[4] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base ./loading.jl:1094
mutable struct Params
:: Float64
x:: Float64
yend
= Params(0.4, 0.2) pos
Params(0.4, 0.2)
= 0.5 pos.x
0.5
Parameterized types are data types that are defined to handle values identically regardless of the type of those values.
Arrays are a familiar example. An Array{T,1}
is a one-dimensional array filled with objects of any type T
(e.g. Float64
, String
).
# Defining a parametric point
struct Duple{T} # T is a parameter to the type Duple
::T
x::T
yend
Duple(3, -1.0)
MethodError: no method matching Duple(::Int64, ::Float64)
Closest candidates are:
Duple(::T, ::T) where T at In[29]:3
Stacktrace:
[1] top-level scope
@ In[30]:1
[2] eval
@ ./boot.jl:373 [inlined]
[3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base ./loading.jl:1196
Int64} Duple{
Duple{Int64}
This single declaration defines an unlimited number of new types: Duple{String}
, Duple{Float64}
, etc. are all immediately usable.
Duple(1.3, 3.4)
Truple3{Float64, Int64}((3.4, 5), 4)
Duple("Hello", "Your")
We can also restrict the type parameter T
using the type hierarchy.
typeof("S")
String
typeof("S") <: Number
false
typeof(4.6) <: Float64
true
Float64 <: Number
true
# T can be any subtype of Number, but nothing else
struct PlanarCoordinate{T<:Number}
::T
x::T
yend
PlanarCoordinate("4th Ave", "14th St")
LoadError: MethodError: no method matching PlanarCoordinate(::String, ::String)
MethodError: no method matching PlanarCoordinate(::String, ::String)
Stacktrace:
[1] top-level scope
@ In[49]:1
[2] eval
@ ./boot.jl:373 [inlined]
[3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base ./loading.jl:1196
PlanarCoordinate(2//3, 8//9)
PlanarCoordinate{Rational{Int64}}(2//3, 8//9)
Number:
349//80 + 3//4 # rational
409//80
typeof( factorial(20) )
Int64
typeof( big(20) )
BigInt
You can write all your code without thinking about types at all. If you do this, however, you’ll be missing out on some of the biggest benefits of using Julia.
If you understand types, you can:
Even if you only use built-in functions and types, your code still takes advantage of Julia’s type system. That’s why it’s important to understand what types are and how to use them.
# Example: writing type-stable functions
function sumofsins_unstable(n::Integer)
= 0:: Integer
sum for i in 1:n
+= sin(3.4)
sum end
return sum
end
function sumofsins_stable(n::Integer)
= 0.0 :: Float64
sum for i in 1:n
+= sin(3.4)
sum end
return sum
end
# Compile and run
sumofsins_unstable(Int(1e5))
sumofsins_stable(Int(1e5))
-25554.110202663698
@time sumofsins_unstable(Int(1e5))
0.000268 seconds
-25554.110202663698
@time sumofsins_stable(Int(1e5))
0.000130 seconds
-25554.110202663698
In sumofsins_stable
, the compiler is guaranteed that sum
is of type Float64
throughout; therefore, it saves time and memory. On the other hand, in sumofsins_unstable
, the compiler must check the type of sum
at each iteration of the loop. Let’s look at the LLVM intermediate representation.
So far we have defined functions over argument lists of any type. Methods allow us to define functions “piecewise”. For any set of input arguments, we can define a method, a definition of one possible behavior for a function.
# Define one method of the function print_type
function print_type(x::Number)
println("$x is a number")
end
print_type (generic function with 1 method)
# Define another method
function print_type(x::String)
println("$x is a string")
end
print_type (generic function with 2 methods)
# Define yet another method
function print_type(x::Number, y::Number)
println("$x and $y are both numbers")
end
print_type (generic function with 3 methods)
# See all methods for a given function
methods(print_type)
Julia uses multiple dispatch to decide which method of a function to execute when a function is applied. In particular, Julia compares the types of all arguments to the signatures of the function’s methods in order to choose the applicable one, not just the first (hence “multiple”).
print_type(5)
5 is a number
print_type("foo")
foo is a string
print_type([1, 2, 3])
MethodError: MethodError: no method matching print_type(::Array{Int64,1})
Closest candidates are:
print_type(!Matched::String) at In[53]:3
print_type(!Matched::Number) at In[51]:3
print_type(!Matched::Number, !Matched::Number) at In[54]:3
MethodError: no method matching print_type(::Array{Int64,1})
Closest candidates are:
print_type(!Matched::String) at In[53]:3
print_type(!Matched::Number) at In[51]:3
print_type(!Matched::Number, !Matched::Number) at In[54]:3
Stacktrace:
[1] top-level scope at In[58]:1
Julia supports a short function definition for one-liners
f(x::Float64) = x^2.0
f(x::Int64) = x^3
As well as a special syntax for anonymous functions
->u^2 u
map(u->u^2, [1,2,3,4])
f(a,b,c=true; algo="newton")
UndefVarError: UndefVarError: f not defined
UndefVarError: f not defined
Stacktrace:
[1] top-level scope at In[59]:1
As we’ve seen, you can use Julia just like you use MATLAB and get faster code. However, to write faster and better code, attempt to write in a “Julian” manner:
How is Julia so fast? Julia is just-in-time (JIT) compiled, which means (according to this StackExchange answer):
A JIT compiler runs after the program has started and compiles the code (usually bytecode or some kind of VM instructions) on the fly (or just-in-time, as it’s called) into a form that’s usually faster, typically the host CPU’s native instruction set. A JIT has access to dynamic runtime information whereas a standard compiler doesn’t and can make better optimizations like inlining functions that are used frequently.
This is in contrast to a traditional compiler that compiles all the code to machine language before the program is first run.
In particular, Julia uses type information at runtime to optimize how your code is compiled. This is why writing type-stable code makes such a difference in speed!
Taken from QuantEcon’s Julia Essentials and Vectors, Arrays, and Matrices lectures.
Consider the polynomial enumerate
, write a function p
such that p(x, coeff)
computes the value of the polynomial with coefficients coeff
evaluated at x
.
Write a function solve_discrete_lyapunov
that solves the discrete Lyapunov equation