A Practical Introduction to

Joris Kraak, Senior Software Engineer

  • M.Sc. in Electrical Engineering
  • Tinkering since the C64
  • Coding for ~20 years
  • Working at GN since 2010

Opinions expressed are my own and do not (necessarily) reflect GN’s

Why ?

is an open-source language for high-performance technical computing and data science
is a multi-paradigm programming language

Procedural Programming?

Functions and variables

Object-Oriented Programming?

Sort of…

Multiple dispatch

Encapsulation

Functional Programming?

Functions are first-class citizens

No strict enforcement of immutability

can accomodate most popular programming paradigms (through convention)

has a dynamic, nominative and parametric type system

Values can be specified to be of a certain type

Types are not optional, but can be omitted

Dynamic & “Optional”


              julia> 1 + 1
              2

              julia> 1 + 1.0
              2.0

              julia> 1::Int64 + 1.0::Float64
              2.0

              julia> (1 + 1.0)::Int64
              ERROR: TypeError: typeassert: expected Int64, got Float64
            

Dynamic & “Optional”


              julia> Int64(1 + 1.0)
              2

              julia> Int64(1 + 1.5)
              ERROR: InexactError()
            

Nominative Type System

Types are compatible if and only if they are explicitly declared to be

Types are declared in a tree, where concrete types are leafs and abstract types are branch nodes

Nominative Type System


              julia> supertype(Int64)
              Signed

              julia> supertype(supertype(Int64))
              Integer

              julia> supertype(supertype(supertype(Int64)))
              Real

              julia> supertype(supertype(supertype(supertype(Int64))))
              Number

              julia> supertype(supertype(supertype(supertype(supertype(Int64)))))
              Any
            

Composite Types


            julia> struct Point
              x::Int
              y::Int
            end

            julia> point_a = Point(1, 2)
            Point(1, 2)

            julia> point_a.x
            1

            julia> point_a.y
            2
            

Parametric Types


            julia> struct Pointy{S<:Number}
              x::S
              y::S
            end

            julia> point_b = Pointy(1.0, 2.0)
            Pointy{Float64}(1.0, 2.0)

            julia> point_b.x
            1.0

            julia> point_a.y
            2.0
            

Parametric Types


            julia> point_c = Pointy(1, 2.0)
            ERROR: MethodError: no method matching Pointy(::Float64, ::Int64)
            Closest candidates are:
              Pointy(::S<:Number, ::S<:Number) where S<:Number at REPL[13]:2
            

There's a lot more to types

But let’s stop here

In , functions are objects that map a tuple of argument values to a return value

Defining Functions


              julia> function my_plus(x, y)
                x + y
              end
              my_plus (generic function with 1 method)

              julia> my_plus
              my_plus (generic function with 1 method)

              julia> my_plus(1, 2)
              3
            

“varargs”


            julia> print_squares(args...) = foreach(
              arg -> println(arg ^ 2), args
            )
            print_squares (generic function with 1 method)

            julia> print_squares(1, 2, 3)
            1
            4
            9
            

Optional & Keyword Arguments


            julia> pow(base = 2; exponent = 3) = base ^ exponent
            pow (generic function with 2 methods)

            julia> pow()
            8

            julia> pow(3)
            27

            julia> pow(exponent = 2)
            4
            

Methods & Dispatch


            julia> my_plus(x::String, y::Any) = "$(x)$(y)"
            my_plus (generic function with 2 methods)

            julia> my_plus(x::String, y::Float64) = "$(y)$(x)"
            my_plus (generic function with 3 methods)
            

            julia> my_plus("Hello", 1)
            "Hello1"

            julia> my_plus("Goodbye", 5.0)
            "5.0Goodbye"
            

Vectorizing Functions

Implement specialized methods for Array

Broadcasting


            julia> pow.([1, 2, 3])
            3-element Array{Int64,1}:
            1
            8
            27

            julia> broadcast(pow, [1, 2, 3])
            3-element Array{Int64,1}:
            1
            8
            27
            

Advanced Topics

Metaprogramming

represents its own code as a data structure of the language itself


              julia> sum_one_and_one = "1 + 1"
              "1 + 1"

              julia> sum_one_and_one_expression = parse(sum_one_and_one)
              :(1 + 1)

              julia> eval(sum_one_and_one_expression)
              2
              

              julia> dump(sum_one_and_one_expression)
              Expr
                head: Symbol call
                args: Array{Any}((3,))
                  1: Symbol +
                  2: Int64 1
                  3: Int64 1
                typ: Any

              julia> sum_one_and_one_expression.args[1] = :-;

              julia> eval(sum_one_and_one_expression)
              0
              

Macros

Receive, operate on and return Expressions

Can’t functions exhibit this same behavior?

Macros execute when code is parsed


            julia> macro my_show(x)
              print("$x = ")
              x
            end
            @my_show (macro with 1 method)

            julia> @my_show 5 * 12345
            5 * 12345 = 61725
            

What’s the use?

Extending the language

Simplifying repetitive or error-prone tasks

Field Dependencies in Structs

Configuration and derived fields

Encode dependencies in functions

setField! for ‘configuration fields’

deriveField! for ‘derived fields’


            julia> @dependent mutable struct Powers
              base::Int

              cubed::Int :base
              squared::Int :base

              Powers(base::Int) = setField!(new(base), :base, base)
            end;

            julia> calculateCubed(base::Int) = base ^ 3;

            julia> calculateSquared(base::Int) = base ^ 2;
            

            julia> Powers(5)
            Powers(
                base::Int64 = 5
                squared::Int64 = 25
                cubed::Int64 = 125
            )
            

Probabilistic Graphical Models

DSL for specifying probabilistic models over random variables

Model a Coin Toss


            julia> FactorGraph(); # Start a new model

            julia> outcome = Variable(id=:outcome);
            julia> Bernoulli(outcome, 0.5);
            julia> outcome
            ForneyLab.Variable(:outcome, Edges:
            Edge belonging to variable outcome:
              ( bernoulli_1.i[out] )----( NONE ).
            )
            

Doesn’t match well with mathematical notation

Becomes cumbersome fast for larger graphs


            julia> FactorGraph(); # Start a new model

            julia> @RV outcome ~ Bernoulli(0.5)
            ForneyLab.Variable(:outcome, Edges:
            Edge belonging to variable outcome:
              ( bernoulli_1.i[out] )----( NONE ).
            )
            

Language Interoperability

Built-in support for calling C and Fortran

Community supported packages for other languages (Java, Python, etc.)

Calling C

Provide a libary to call, specify input and output argument types and input arguments


            julia> ccall((:clock, "libc"), Int32, ())
            2035418

            julia> path = ccall(
              (:getenv, "libc"), Cstring, (Cstring,), "SHELL"
            )
            Cstring(0x00007ffee95abd25)

            julia> unsafe_string(path)
            "/bin/zsh"
            

Audio Streaming

JACK Audio Connection Kit

Callback mechanism using buffers

Callback can be a function!

JACKAudio.jl

Modules

Provide encapsulation, can be nested and precompiled

moduleend

export, using, import

Package Management

Packages are named following a PackageName.jl scheme

Packages are Git repositories

Centralized metadata repository

Wrapped in Pkg module

Dependency Management

Specified in REQUIRE files


              julia 0.6

              HttpCommon 0.4.0 0.5.0
              HttpServer 0.3.0
              JSON 0.16.4
              Mux 0.2.3
              WebSockets 0.4.0
            
https://pkg.julialang.org

Testing

Built-in basic testing framework Base.Test

By convention in /test with a runtests.jl entrypoint

Pkg.test("MyPackageName")


            module MyMathTest

            using Base.Test
            import MyMath: square

            @testset "bar" begin
              @testset "integers" begin
                @test square(1) === 1
                @test square(2) === 4
              end

              @test_throws(MethodError, square("foo"))
            end

            end
            

Code Coverage

Coverage.jl

Line coverage exportable to lcov format

Memory allocation

Debugging…
Gallium

IDEs…

Breaking Versions…

Version 1.0…

Soon tm

Getting Started

JuliaBox, JuliaPro or (plain) Julia
Julia.jl (a curated list of resources)

Thanks for listening!

Credits

Social Media Icons
Simple Icons
Julia Logo
Stefan Karpinski
GN, ReSound & Jabra Logos & Product Photos
GN Store Nord A/S
Statler & Waldorf
The Walt Disney Company
Wheatley in Space
Lomoco
Rainbow Dash
Hasbro
Bob the Builder
Keith Chapman