An Interactive Introduction to Julia

THW, April 7 2020

Cail Daley

Three mental models and a rabbit hole

People often like to say that Julia "walks like python, runs like C."

But this doesn't capture any of the things that have made me love the language!

Instead, let's look at three mental models and a rabbit hole (lifted from A Mental Model for Julia by Chris Rackauckas)

A Mental Model for Python/R/MATLAB: Talking to a Politician

  • These scripting languages were developed to "be easy".
  • You tell them something, and they try to give you what you want.
  • There may be some things hidden behind the scenes to make everything "work better".
  • They may not give you the fastest reply.

A Mental Model for C/Fortran: Talking to a Philosopher

  • You say something, and they want something more specific.
  • You spend hours digging deep into the specifics of something.
  • After finally getting it right, you know how to quickly get a specific answer from them.
  • Everytime you want to talk about something new, you have to start all the way at the basics again.

A Mental Model for Julia: Talking to a Scientist

  • When you're talking, everything looks general. However, you really mean very specific details determined by context.
  • You can quickly dig deep into a subject, assuming many rules, theories, and terminology.
  • Nothing is hidden: if you ever want to hear about every little detail, you can ask.
  • They will get mad (and throw errors at you) if you begin to be loose with the specific details.

The Rabbit Hole

another perspective, excerpted from a discussion the creators of Julia that can be found here.

Karpinski: ...it's [a] very straightforward dynamic language to use. But then there's sort of this rabbit hole of advanced features that you can go down that you don't need to know right away to write useful programs, but which can help you as you find yourself doing harder and harder things.

Edelman: What happens when you start to go down this rabbit hole is you become a better programmer, something for when you used these other languages you never knew you were missing, and never knew you wanted to be. But then when you do it, you wonder how you lived without it.

Julia Syntax

Hello World...

In [180]:
x = 2π
println("Hello world, the circumferene of a circle is $x times its radius")
typeof(π)
Hello world, the circumferene of a circle is 6.283185307179586 times its radius
Out[180]:
Irrational{:π}

Arrays

In [190]:
# 1D, 2D (; vs _?), list comprehension, basic operations   
a = [0 1; 1 3]
b = [i for i in 1:3, j in 1:3]
Out[190]:
3×3 Array{Int64,2}:
 1  1  1
 2  2  2
 3  3  3
In [193]:
a + a
Out[193]:
2×2 Array{Int64,2}:
 0  2
 2  6

Broadcasting

In [217]:
a = [π/2 π; 3π/2 2π]


println(sin.(a), "this is a new line: \n")
println()
println(sin(a))

a / a
a * inv(a)

? +
[1.0 1.2246467991473532e-16; -1.0 -2.4492935982947064e-16]this is a new line: 


[-0.2209579211114544 0.482490682615219; 0.7237360239228284 0.502778102811374]
syntax: invalid identifier name "?"

Indexing

Yes, Julia uses 1-based indexing and column-major ordering (like Fortran, Matlab)

you'll get used to it...

In [242]:
# ranges & splatting, CartesianIndices
 a = [0 1; 3 4]

x = 1:10

xy = (1,2)
f(x,y) = x+y
f(xy...)

CartesianIndices(a)
Out[242]:
2×2 CartesianIndices{2,Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}}:
 CartesianIndex(1, 1)  CartesianIndex(1, 2)
 CartesianIndex(2, 1)  CartesianIndex(2, 2)

Control Flow

Unlike Python, Julia doesn't care about indentation and instead uses end to break out of something:

In [244]:

1

Functions and Compilation

In [1]:
z(x) = [x^2 for i in 1:1000]
Out[1]:
z (generic function with 1 method)
In [2]:
@time z(10)
@time z(10);
  0.000003 seconds (1 allocation: 7.938 KiB)
  0.000003 seconds (1 allocation: 7.938 KiB)

Types in Julia

(where things start to get cool...)

Julia is optionally typed, combining elements of both dynamic and static type systems.

The Types section of the docs explores all the nuances of this, but here's a hands-on exploration:

Type Inspection

In [265]:
#typeof, supertype, concrete vs abstract

t = typeof(1.0); println(t)
for i in 1:5
    t = supertype(t);
    println(t)
end

isabstracttype(Real)
Float64
AbstractFloat
Real
Number
Any
Any
Out[265]:
true

"Methods" and Multiple Dispatch

In [5]:
#+, methods, @code_llvm

myadd(x,y) = x+y
myadd(x::Complex ,y) = real(x)+y


# myadd(y, x::Complex) = myadd(y, x)
# above line creates an infinite loop.. had to switch x and y
myadd(y, x::Complex) = myadd(x, y)
Out[5]:
myadd (generic function with 3 methods)
In [7]:
println(myadd(1,2 + 3im))
println(myadd(2 + 1im, 3))
3
5

Historic Numbers: Making our own type system

In [279]:
struct HistoricNumber
    num::Number
    ops::Int
end

HistoricNumber(num::Number) = HistoricNumber(num, 0)

import Base: +
+(a::HistoricNumber, b::HistoricNumber) = HistoricNumber(a.num + b.num, max(a.ops, b.ops) + 1)
+(a::HistoricNumber, b::Number) = HistoricNumber(a.num + b, a.ops + 1)
+(a::Number, b::HistoricNumber) = b + a
Out[279]:
+ (generic function with 193 methods)
In [282]:
n = HistoricNumber(1)

for i in 1:rand(1:1000)
    n += randn()
end
n
Out[282]:
HistoricNumber(-10.41778531348284, 558)

Bonus Tidbits

(if there's time)

Plotting

In [ ]:

Calling Python or C

In [286]:
using PyCall

py"""
import pandas as pd
"""

py"pd.DataFrame($a)"

ccall(:sqrt, Float64, (Float64,), 2)
Out[286]:
1.4142135623730951

Autodifferentiation

In [ ]:

Resources

If you're tempted to play around with Julia, the following may be of use:

Thanks!

Enjoy the rabbit hole...