Julia Basics

What is Julia

  • developped at MIT on top of opensource technologies
    • linux / git / llvm
  • syntax inspired by Matlab but:
    • more consistent
    • lots of features from high level languages
  • everything is JIT-compiled
    • interpreted vs compiled treadeoff
    • -> very fast
    • most of the base library is written in Julia
  • opensource/free + vibrant community

Some useful links from QuantEcon:

Excellent resources at: julialang - checkout JuliaAcademy, it’s free

an example of what you shouldn’t do in Matlab

How I learnt: interpreted code is slow, so vectorize your coe.

"""
Multplies I by J by K
"""
function stupid_loop(I,J,K)
    t = 0.0
    for i=1:I
        for j=1:J
            for k = 1:K
                t += 1.0
            end        
        end
    end
    return t
end
stupid_loop
@time stupid_loop(1000,1000,1000)
  0.817857 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_7565

.LCPI0_0:

    .quad   0x3ff0000000000000              # double 1

    .section    .ltext,"axl",@progbits

    .globl  julia_stupid_loop_7565

    .p2align    4, 0x90

    .type   julia_stupid_loop_7565,@function

julia_stupid_loop_7565:                 # @julia_stupid_loop_7565

; Function Signature: stupid_loop(Int64, Int64, Int64)

; ┌ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:4 within `stupid_loop`

# %bb.0:                                # %top

    #DEBUG_VALUE: stupid_loop:I <- $rdi

    #DEBUG_VALUE: stupid_loop:J <- $rsi

    #DEBUG_VALUE: stupid_loop:K <- $rdx

    xor  eax, eax

    vxorpd   xmm0, xmm0, xmm0

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl within `stupid_loop`

    test rdi, rdi

    cmovg    rax, rdi

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:6 within `stupid_loop`

    jle  .LBB0_28

# %bb.1:                                # %L16.preheader

    xor  ecx, ecx

    test rsi, rsi

    cmovg    rcx, rsi

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:7 within `stupid_loop`

    jle  .LBB0_28

# %bb.2:                                # %L16.preheader

    test rdx, rdx

    jle  .LBB0_28

# %bb.3:                                # %L16.preheader36

    push rbp

    mov  rbp, rsp

    push r14

    push rbx

    mov  rdi, rdx

    sar  rdi, 63

    andn rdx, rdi, rdx

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:8 within `stupid_loop`

    lea  rdi, [rdx - 1]

    mov  r8, rdx

    and  r8, -8

    mov  r9d, edx

    and  r9d, 7

    vxorpd   xmm0, xmm0, xmm0

    mov  r10d, 1

    movabs   r11, offset .LCPI0_0

    vmovsd   xmm1, qword ptr [r11]           # xmm1 = mem[0],zero

    movabs   r11, 9223372036854775806

    and  r11, rcx

    jmp  .LBB0_4

    .p2align    4, 0x90

.LBB0_26:                               # %L74.loopexit.split

                                        #   in Loop: Header=BB0_4 Depth=1

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:12 within `stupid_loop`

; │┌ @ range.jl:921 within `iterate`

    lea  rbx, [r10 + 1]

; ││┌ @ promotion.jl:637 within `==`

    cmp  r10, rax

    mov  r10, rbx

; │└└

    je   .LBB0_27

.LBB0_4:                                # %L16

                                        # =>This Loop Header: Depth=1

                                        #     Child Loop BB0_6 Depth 2

                                        #       Child Loop BB0_8 Depth 3

                                        #       Child Loop BB0_11 Depth 3

                                        #       Child Loop BB0_14 Depth 3

                                        #       Child Loop BB0_17 Depth 3

                                        #     Child Loop BB0_22 Depth 2

                                        #     Child Loop BB0_25 Depth 2

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:8 within `stupid_loop`

    cmp  rsi, 1

    jne  .LBB0_5

.LBB0_19:                               # %L74.loopexit.split.unr-lcssa

                                        #   in Loop: Header=BB0_4 Depth=1

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`

    test cl, 1

    je   .LBB0_26

# %bb.20:                               # %L33.epil.preheader

                                        #   in Loop: Header=BB0_4 Depth=1

    cmp  rdi, 7

    jb   .LBB0_23

# %bb.21:                               # %L33.new.epil

                                        #   in Loop: Header=BB0_4 Depth=1

    mov  rbx, r8

    .p2align    4, 0x90

.LBB0_22:                               # %L50.epil39

                                        #   Parent Loop BB0_4 Depth=1

                                        # =>  This Inner Loop Header: Depth=2

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`

; │┌ @ float.jl:495 within `+`

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

; │└

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`

    add  rbx, -8

    jne  .LBB0_22

.LBB0_23:                               # %L63.loopexit.unr-lcssa.epil

                                        #   in Loop: Header=BB0_4 Depth=1

    test dl, 7

    je   .LBB0_26

# %bb.24:                               # %L50.epil.epil.preheader

                                        #   in Loop: Header=BB0_4 Depth=1

    mov  rbx, r9

    .p2align    4, 0x90

.LBB0_25:                               # %L50.epil.epil

                                        #   Parent Loop BB0_4 Depth=1

                                        # =>  This Inner Loop Header: Depth=2

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`

; │┌ @ float.jl:495 within `+`

    vaddsd   xmm0, xmm0, xmm1

; │└

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`

    dec  rbx

    jne  .LBB0_25

    jmp  .LBB0_26

    .p2align    4, 0x90

.LBB0_5:                                # %L16.new

                                        #   in Loop: Header=BB0_4 Depth=1

    xor  ebx, ebx

    jmp  .LBB0_6

    .p2align    4, 0x90

.LBB0_18:                               # %L63.loopexit.1

                                        #   in Loop: Header=BB0_6 Depth=2

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:11 within `stupid_loop`

    add  rbx, 2

    cmp  rbx, r11

    je   .LBB0_19

.LBB0_6:                                # %L33

                                        #   Parent Loop BB0_4 Depth=1

                                        # =>  This Loop Header: Depth=2

                                        #       Child Loop BB0_8 Depth 3

                                        #       Child Loop BB0_11 Depth 3

                                        #       Child Loop BB0_14 Depth 3

                                        #       Child Loop BB0_17 Depth 3

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`

    cmp  rdi, 7

    jb   .LBB0_9

# %bb.7:                                # %L33.new

                                        #   in Loop: Header=BB0_6 Depth=2

    mov  r14, r8

    .p2align    4, 0x90

.LBB0_8:                                # %L50

                                        #   Parent Loop BB0_4 Depth=1

                                        #     Parent Loop BB0_6 Depth=2

                                        # =>    This Inner Loop Header: Depth=3

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`

; │┌ @ float.jl:495 within `+`

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

; │└

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`

    add  r14, -8

    jne  .LBB0_8

.LBB0_9:                                # %L63.loopexit.unr-lcssa

                                        #   in Loop: Header=BB0_6 Depth=2

    test dl, 7

    je   .LBB0_12

# %bb.10:                               # %L50.epil.preheader

                                        #   in Loop: Header=BB0_6 Depth=2

    mov  r14, r9

    .p2align    4, 0x90

.LBB0_11:                               # %L50.epil

                                        #   Parent Loop BB0_4 Depth=1

                                        #     Parent Loop BB0_6 Depth=2

                                        # =>    This Inner Loop Header: Depth=3

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`

; │┌ @ float.jl:495 within `+`

    vaddsd   xmm0, xmm0, xmm1

; │└

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`

    dec  r14

    jne  .LBB0_11

.LBB0_12:                               # %L63.loopexit

                                        #   in Loop: Header=BB0_6 Depth=2

    cmp  rdi, 7

    jb   .LBB0_15

# %bb.13:                               # %L33.new.1

                                        #   in Loop: Header=BB0_6 Depth=2

    mov  r14, r8

    .p2align    4, 0x90

.LBB0_14:                               # %L50.1

                                        #   Parent Loop BB0_4 Depth=1

                                        #     Parent Loop BB0_6 Depth=2

                                        # =>    This Inner Loop Header: Depth=3

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`

; │┌ @ float.jl:495 within `+`

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

    vaddsd   xmm0, xmm0, xmm1

; │└

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`

    add  r14, -8

    jne  .LBB0_14

.LBB0_15:                               # %L63.loopexit.unr-lcssa.1

                                        #   in Loop: Header=BB0_6 Depth=2

    test dl, 7

    je   .LBB0_18

# %bb.16:                               # %L50.epil.1.preheader

                                        #   in Loop: Header=BB0_6 Depth=2

    mov  r14, r9

    .p2align    4, 0x90

.LBB0_17:                               # %L50.epil.1

                                        #   Parent Loop BB0_4 Depth=1

                                        #     Parent Loop BB0_6 Depth=2

                                        # =>    This Inner Loop Header: Depth=3

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:9 within `stupid_loop`

; │┌ @ float.jl:495 within `+`

    vaddsd   xmm0, xmm0, xmm1

; │└

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:10 within `stupid_loop`

    dec  r14

    jne  .LBB0_17

    jmp  .LBB0_18

.LBB0_27:

    pop  rbx

    pop  r14

    pop  rbp

.LBB0_28:                               # %L85

; │ @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_W4sZmlsZQ==.jl:13 within `stupid_loop`

    ret

.Lfunc_end0:

    .size   julia_stupid_loop_7565, .Lfunc_end0-julia_stupid_loop_7565

; └

                                        # -- End function

    .type   ".L+Core.Float64#7567",@object  # @"+Core.Float64#7567"

    .section    .lrodata,"al",@progbits

    .p2align    3, 0x0

".L+Core.Float64#7567":

    .quad   ".L+Core.Float64#7567.jit"

    .size   ".L+Core.Float64#7567", 8



.set ".L+Core.Float64#7567.jit", 123748261767136

    .size   ".L+Core.Float64#7567.jit", 8

    .section    ".note.GNU-stack","",@progbits

Syntax Review

Variable assignment

Assignement operator is = (equality is ==, identity is ===)

# Assign the value 10 to the variable x
x = 10
10
2 == 3
false
# Variable names can have Unicode characters
# To get ϵ in the REPL, type \epsilon<TAB>

α =0
ϵ = 1e-4

🦄 = 1.23
1.23

Default semantic is pass-by-reference:

a = [1,2,3,4]
b = a
a[1] = 10
b
4-element Vector{Int64}:
 10
  2
  3
  4

To work on a copy: copy or deepcopy

a = [1,2,3,4]
b = copy(a)
a[1]=10
b
4-element Vector{Int64}:
 1
 2
 3
 4
a == b
true
a .== b
4-element BitVector:
 0
 1
 1
 1
a === b
false

Basic types

# for any object `typeof` returns the type
typeof?
Base.Meta.ParseError: ParseError:
# Error @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_X31sZmlsZQ==.jl:1:1
?typeof
╙ ── not a unary operator
ParseError:

# Error @ /home/pablo/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_X31sZmlsZQ==.jl:1:1

?typeof

╙ ── not a unary operator



Stacktrace:

 [1] top-level scope

   @ ~/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_X31sZmlsZQ==.jl:1
typeof(a)
Vector{Int64} (alias for Array{Int64, 1})

Numbers

y = 2 + 2
4
-y
0.34*23
3/4
# Scalar multiplication doesn't require *
3(4 - 2)
6
x = 4
2x + 2x^2
40
typeof(x)
Int64
sizeof(a)
32
bitstring(x)
"0000000000000000000000000000000000000000000000000000000000000100"

Booleans

true
true

Equality

0 == 1
false
2 != 3
3 <= 4

Identity

a = [34, 35]
b = [34, 35]
c = a
c === a
b === a

Boolean operator

true && false
true || false
!true

Strings

# Strings are written using double quotes
str = "This is a string"
"This is a string"
ch = 'k' # this is a character
'k': ASCII/Unicode U+006B (category Ll: Letter, lowercase)
# Strings can also contain Unicode characters
fancy_str = "α is a string"
"α is a string"
# String interpolation using $
# The expression in parentheses is evaluated and the result is 
# inserted into the string
a = 2+2
"2 + 2 = $(a)"
"2 + 2 = 4"
println("It took me $(a) iterations")
It took me 4 iterations
# String concatenation using *
"hello" + "world"
MethodError: MethodError: no method matching +(::String, ::String)
The function `+` exists, but no method is defined for this combination of argument types.
String concatenation is performed with * (See also: https://docs.julialang.org/en/v1/manual/strings/#man-concatenation).

Closest candidates are:
  +(::Any, ::Any, !Matched::Any, !Matched::Any...)
   @ Base operators.jl:642
  +(!Matched::BitMatrix, !Matched::LinearAlgebra.UniformScaling)
   @ LinearAlgebra ~/.julia/juliaup/julia-1.12.4+0.x64.linux.gnu/share/julia/stdlib/v1.12/LinearAlgebra/src/uniformscaling.jl:154
  +(!Matched::Bool, !Matched::Complex{Bool})
   @ Base complex.jl:308
  ...

MethodError: no method matching +(::String, ::String)

The function `+` exists, but no method is defined for this combination of argument types.

String concatenation is performed with * (See also: https://docs.julialang.org/en/v1/manual/strings/#man-concatenation).



Closest candidates are:

  +(::Any, ::Any, !Matched::Any, !Matched::Any...)

   @ Base operators.jl:642

  +(!Matched::BitMatrix, !Matched::LinearAlgebra.UniformScaling)

   @ LinearAlgebra ~/.julia/juliaup/julia-1.12.4+0.x64.linux.gnu/share/julia/stdlib/v1.12/LinearAlgebra/src/uniformscaling.jl:154

  +(!Matched::Bool, !Matched::Complex{Bool})

   @ Base complex.jl:308

  ...





Stacktrace:

 [1] top-level scope

   @ ~/Teaching/econobits/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_Y103sZmlsZQ==.jl:2
println("hello ", "world")

Arrays

Julia has one-dimensional arrays. They are also called Vector.

A = [1, 2]
2-element Array{Int64,1}:
 1
 2
sizeof(A)
16
typeof(A) == Vector{Int64}
# vectors have one dimension: they are indexed by an integer
A[1]
1

2d arrays are also called matrices… and can be used for matrix multiplications.

B = [0.1 0.2 0.3; 4 5 6]
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:

B[1,2]
0.2
B[:,1]
2-element Array{Float64,1}:
 0.1
 4.0
B[:,1:end-1]
2×2 Array{Float64,2}:
 0.1  0.2
 4.0  5.0

Control flow

Conditions

x = -3
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

i = 3
while i > 0
    println(i)
    i = i - 1
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
cities = ["Boston", "New York", "Philadelphia"]
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

List comprehensions

[i^2 for i=1:10]
10-element Array{Int64,1}:
   1
   4
   9
  16
  25
  36
  49
  64
  81
 100
[i^2 for i=1:10 if mod(i,2)==0]
5-element Array{Int64,1}:
   4
  16
  36
  64
 100

Function definitions

Basic functions

function f(a,b,c)
    res = a + b +c
    return res
end
f (generic function with 1 method)
f(3,4,3)
10

Optional arguments

function f(a,b,c=1)
    res = a + b +c
    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)
    res = operator(a, operator( b , c) )
    if add_one
        res +=1
    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:

fun = (x,y) -> x^2 + 1 + y
#12 (generic function with 1 method)
f(1,2,3; operator=fun)
10

Data Types and multiple dispatch

Composite types

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
    name
end
s = Parameter_without_types(4.4, "no type at all")
Parameter_without_types(4.4, "no type at all")
# Type definition
struct Parameter
    value::Float64
    transformation::Function # Function is a type!
    tex_label::String
    description::String
end
Parameter(4,fun,"\\Gamma", "My parameter")
Parameter(4.0, var"#12#13"(), "\\Gamma", "My parameter")

When a type with \(n\) fields is defined, a constructor (function that creates an instance of that type) that takes \(n\) ordered arguments is automatically created. Additional constructors can be defined for convenience.

# 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 )
# 6 methods for type constructor:
  • Parameter(value::Float64, transformation::Function, tex_label::String, description::String) in Main at In[35]:3
  • Parameter(value, tex::String) in Main at In[55]:2
  • Parameter(value, transformation::Function) in Main at In[55]:3
  • Parameter(value, transformation) in Main at In[50]:2
  • Parameter(value, transformation, tex) in Main at In[45]:1
  • Parameter(value, transformation, tex_label, description) in Main at In[35]:3
# Alternative constructors end with an appeal to the default
# constructor
function Parameter(value::Float64, tex_label::String)
    transformation = identity
    description = "No description available"
    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)
# 4 methods for type constructor:
  • Parameter(value::Float64, transformation::Function, tex_label::String, description::String) in Main at In[1]:3
  • Parameter(value::Float64, tex_label::String) in Main at In[8]:4
  • Parameter(value, transformation, tex) in Main at In[5]:1
  • Parameter(value, transformation, tex_label, description) in Main at In[1]:3
# 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
α.value = 0.75

Mutable vs non mutable types

by default structures in Julia are non-mutable

β.value = 0.6
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
    x:: Float64
    y:: Float64
end
pos = Params(0.4, 0.2)
Params(0.4, 0.2)
pos.x = 0.5
0.5

Parameterized Types

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
    x::T
    y::T
end
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
Duple{Int64}
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}
    x::T
    y::T
end
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

Why Use Types?

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:

  • Write faster code
  • Write expressive, clear, and well-structured programs (keep this in mind when we talk about functions)
  • Reason more clearly about how your code works

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)  
    sum = 0:: Integer
    for i in 1:n  
        sum += sin(3.4)  
    end  
    return sum 
end  

function sumofsins_stable(n::Integer)  
    sum = 0.0 :: Float64
    for i in 1:n  
        sum += sin(3.4)  
    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.

Multiple Dispatch

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)
# 3 methods for generic function print_type:
  • print_type(x::String) in Main at In[53]:3
  • print_type(x::Number) in Main at In[51]:3
  • print_type(x::Number, y::Number) in Main at In[54]:3

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

Other types of functions

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->u^2
map(u->u^2, [1,2,3,4])

Keyword arguments and optional arguments

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

Writing Julian Code

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:

  • Define composite types as logically needed
  • Write type-stable functions for best performance
  • Take advantage of multiple dispatch to write code that looks like math
  • Add methods to existing functions

Just-in-Time Compilation

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!

Additional Exercises

Taken from QuantEcon’s Julia Essentials and Vectors, Arrays, and Matrices lectures.

  1. Consider the polynomial \[p(x) = \sum_{i=0}^n a_0 x^0\] Using enumerate, write a function p such that p(x, coeff) computes the value of the polynomial with coefficients coeff evaluated at x.

  2. Write a function solve_discrete_lyapunov that solves the discrete Lyapunov equation \[S = ASA' + \Sigma \Sigma'\] using the iterative procedure \[S_0 = \Sigma \Sigma'\] \[S_{t+1} = A S_t A' + \Sigma \Sigma'\] taking in as arguments the \(n \times n\) matrix \(A\), the \(n \times k\) matrix \(\Sigma\), and a number of iterations.