Julia & graphic enthusiast. Interested in high performance computing, GPUs and machine learning.

# Julia Workshop

## 1. Julia IDEs

### 1.1. Atom

https://atom.io/packages/language-julia

### 1.2. Visual Studio Code

https://github.com/JuliaEditorSupport/julia-vscode

### 1.3. Jupyter

https://github.com/JuliaLang/IJulia.jl

### 1.4. Nextjournal

you're looking at it right now!

• reproducible science in article form
• articles are interactive, fully versioned and automatically reproducible.
• supports Python, R, Clojure and Julia
# query the documentation of a function
doc(+)
# Install Packages
Base.disable_logging(Base.CoreLogging.Error) # disable warnings
# isolate code in Modules
module PlotExample
using Plots; pyplot()
function example()
plot(rand(100) / 3,reg = true,fill = (0, :green))
scatter!(rand(100), markersize = 6, c = :black)
end
end

using .PlotExample

PlotExample.example()

## 2. Julia in one Cell

0.9s
# Function defined for all types T
function func(a::T) where T
T
end

abstract type SuperType end

# inheritance
struct MyType <: SuperType
field::Int
end
# mutable type without inheritance but with Type Parameter
mutable struct OtherType{TypeParam}
field::TypeParam
end

# short function form
func(a::Float64, b::SuperType) = "a string"

func(b::OtherType{T}) where T = T

func(b::OtherType{Int}) = "oh hello there"

# tuple type
func(f, a::Tuple{Any, Any}) = (f(a), f(a))

@show func(1.0, MyType(1))
@show func(OtherType(1im))
@show func(OtherType(1))

# passing function
tuple = (OtherType(0), OtherType(0))
@show func(func, tuple)
# passing closure

closure = x-> x * 2
@show func(closure, (2, 3))
# the same as above, but with do syntax
result = func((2, 3)) do x
x * 2
end
@show result
# construct array
array = [OtherType(1.0), OtherType(1.0)]
# dot syntax - apply a function to all elements of the argument arrays
@show func.(array)
nothing

## 3. Julia Core Strengths

1) Multiple Dispatch + Introspection

2) Efficient Duck Typing

3) Efficient Higher order Functions

4) Interprocedural Optimization

5) Dynamism + Meta programing

==> Performance + High Level programing

### 3.1. Multiple Dispatch + Introspection

There are three core concepts to multiple dispatch:

• dispatch on all argument types
• specialize method body to argument types
• types are compile time constants
dispatch(x::Int) = 1
dispatch(x::String) = 2
dispatch(x::Complex) = 3

test() = dispatch(1) + dispatch("2") + dispatch(1im)
using InteractiveUtils
""" Helper to show the lowered code of a function"""
function show_asts(f, args...)
argtypes = typeof(args)
println("$f($args):")
println(code_typed(f, argtypes, optimize = false))
println("optimized:")
println(code_typed(f, argtypes))
println("------------------------")
return
end
show_asts(dispatch, 1)
show_asts(test)
function dispatch(x, ::Nothing)
isa(x, Int) && return 1
isa(x, String) && return 2
isa(x, Complex) && return 3
end
test(x) = dispatch(1, x) + dispatch("2", x) + dispatch(1im, x)
show_asts(dispatch, 1, nothing)
show_asts(test, nothing)
methods(dispatch)
# generic base
my_func(a, b) = a + b
my_func(a::Float32, b::Float32) = Intrinsics.fmul(a, b)

#### 3.1.1. Enables you to:

• compiled code with static type information inside function body
• offer generic fallbacks for all kind of type combinations
• specialize to type combinations
• add new methods to existing libraries
• efficient duck typing

### 3.2. Duck Typing

In duck typing, an object's suitability is determined by the presence of certain methods and properties, rather than the type of the object itself.

function do_something(a)
quack(a)
end

struct Duck end
quack(::Duck) = println("quoaak")
# by overloading a function, we get quacking & hence can do_something
quack(x::Int) = println("quack")

do_something(Duck())
do_something(1)

#### 3.2.1. Practical Examples

##### 3.2.1.1. Algorithm Visualization
# untyped generic code: perfect to inject our own types :)
function magic(y::AbstractArray, func)
l = length(y)
k = ceil(Int, log2(l))
for j=1:k, i=2^j:2^j:min(l, 2^k)
y[i] = func(y[i-2^(j-1)], y[i])
end
for j=(k-1):-1:1, i=3*2^(j-1):2^j:min(l, 2^k)
y[i] = func(y[i-2^(j-1)], y[i])
end
y
end
X = rand(10)
Y = magic(copy(X), +)
import Base: getindex, setindex!, length, size
mutable struct AccessArray <: AbstractArray{Nothing, 1}
length::Int
history::Vector
end
function AccessArray(length, read = [], history = [])
end
length(A::AccessArray) = A.length
size(A::AccessArray) = (A.length,)
function getindex(A::AccessArray, i)
nothing
end
function setindex!(A::AccessArray, x, i)
end
import Base.+
+(a::Nothing, b::Nothing) = a
# we can now simply feed this new arrya type into magic - and record what it does
A = magic(AccessArray(8), +)
140.7s
using GeometryTypes, Makie

function render(A::AccessArray)
olast = depth = 0
for y in A.history
(any(y .≤ olast)) && (depth += 1)
olast = maximum(y)
end
maxdepth = depth
olast = depth = 0
C = []
for y in A.history
(any(y .≤ olast)) && (depth += 1)
push!(C, ((y...,), A.length, maxdepth, depth))
olast = maximum(y)
end
msize = 0.1
outsize = 0.15
x1 = Point2f0.(first.(first.(first.(C))), last.(C) .+ outsize .+ 0.05)
x2 = Point2f0.(last.(first.(first.(C))), last.(C) .+ outsize .+ 0.05)
x3 = Point2f0.(first.(last.(first.(C))), last.(C) .+ 1)
connections = Point2f0[]

yoff = Point2f0(0, msize / 2)
ooff = Point2f0(0, outsize / 2 + 0.05)
for i = 1:length(x3)
push!(connections, x3[i] .- ooff, x1[i] .+ yoff, x3[i] .- ooff, x2[i] .+ yoff)
end
node_theme = NT(
markersize = msize, strokewidth = 3,
strokecolor = :black, color = (:white, 0.0),
axis = NT(
ticks = NT(ranges = (1:8, 1:5)),
names = NT(axisnames = ("Array Index", "Depth")),
frame = NT(axis_position = :none)
)
)
s = Scene(resolution = (500, 500))
s = scatter!(s, node_theme, x1)
s = scatter!(s, node_theme, x2)
scatter!(x3, color = :white, markersize = 0.2, strokewidth = 4, strokecolor = :black)
scatter!(x3, color = :red, marker = '+', markersize = outsize)
linesegments!(connections, color = :red)
s
end
render(A)
# Check our theory:
Y - [0; Y[1:end-1];] ≈ X

##### 3.2.1.2. Automatic Differentiation
using ForwardDiff

f(x) = x^2 + 2.0x + 3.0

val = 5
dual = ForwardDiff.Dual(val, 1)
dx = f(dual)
@show ForwardDiff.value(dx) == f(val)
@show ForwardDiff.partials(dx)[] == f_grad(val)
using BenchmarkTools
vals = fill(val, 10^6)
duals = ForwardDiff.Dual.(vals, 1)
@btime f($val) @btime f($dual)

@btime f.($vals) @btime f.($duals)
nothing

#### 3.2.2. Enables you to

• compose functionality across packages
• low effort to extend packages
• use your own types that do extra work with generic APIs

### 3.3. Efficient higher order function

using BenchmarkTools
# we annotate mutating functions usually with '!'
function map1!(f::F, dest, A) where F
for i in 1:length(dest)
@inbounds dest[i] = f(A[i])
end
return dest
end
# This is usually how other languages implement higher order functions
function map2!(@nospecialize(f), dest, A)
for i in 1:length(dest)
@inbounds dest[i] = f(A[i])
end
return dest
end
# some heavy functions
@inline test1(x) = x / 10
@noinline test2(x) = test1(x)# some heavy functions
A, dest = rand(10^6), zeros(10^6);
@btime map1!($test1,$dest, $A) @btime map1!($test2, $dest,$A)
@btime map2!($test1,$dest, $A) nothing #### 3.3.1. Enables you to: • implement mapreduce workflows with optimal performance • compose functionality freely ### 3.4. Interprocedural optimization (IPO) #### 3.4.1. What would Python do? import Solver # Probably uses Numpy(C) + Numba import Arithmetic # Maybe CPython + Python? class MyType: ... # CPython vs Numba, # Numpy & CPython vs MyClass # some_function written in pure python? -> no specialization Solver.solve(Arithmetic.some_function, convert(MyType, data)) Julia Making a single Python algorithm fast in isolation is not that hard making those algorithms generic and interface with other packages is hard! • use cython - loose generics and most higher level constructs & needs compilation • use numba - loose substantial subset of language, no IPO • use C & Python - loose everything 😛 using Solver, Arithmetic, GPUArrays, PrecisionFloats data = convert(PrecisionFloats.Float, some_float_data) # fast higher order functions, duck typing, # constant propagation + inlining across packages Solver.solve(Arithmetic.some_function, GPUArray(data)) # Solver doesn't even need to know Arithmetic nor GPUArrays,  Julia ### 3.5. Dynamics str = "1 + 1" expr = Meta.parse(str) eval(expr) expr = quote 1 + 1 end # or the short form expr = :(1 + 1) dump(expr) # dump = detailed output of fields eval(Expr(:call, +, 1, 1)) macro replace_ab(expr, val) map!(expr.args, expr.args) do arg arg in (:a, :b) && return val arg end return expr end @replace_ab(a * b, "hi ") #### 3.5.1. Performance of dynamic constructs ##### 3.5.1.1. Macros - no cost Macros are just a code transformation on the syntax level ##### 3.5.1.2. Eval - scoped performance penalty using BenchmarkTools function worst(a, expr::Expr) f = eval(expr) map!(a, a) do val Base.invokelatest(f, val) end end function best(f, a) map!(f, a, a) end x = rand(10^6) expr = :((a)-> cos(a)) @btime worst($x, expr)

efunc = eval(:((a)-> cos(a)))
@btime best($efunc,$x)
nothing
test(x) = eval(x)
show_asts(test, :(1 + 1))

#### 3.5.2. Generated Functions

@generated function test(a, b)
# Println is not really allowed here - Core.println works though
Core.println(a, " ", b)
return :() # Need to return an Expr
end
test(1, 2)
@generated function test(a, b)
Core.println(a, " ", b)
return :(a + b) # Need to return an Expr
end
test(1, 2)
test(1, 2)
expressions = []
@generated function map_unrolled(f, arg::NTuple{N, Any}) where N
args = map(1:N) do i
:(f(arg[\$i]))
end
expr = Expr(:tuple, args...)
push!(expressions, expr) # Core.println is really ugly for Expr
expr
end
map_unrolled(sin, (1.0, 2.0, 3.0))
expressions