16.5 s
240 ms
238 μs
30.7 ms

Julia type system

  • Julia is a strongly typed language

  • Knowledge about the layout of a value in memory is encoded in its type

  • Prerequisite for performance

  • There are concrete types and abstract types

  • See the Julia WikiBook for more

Concrete types

  • Every value in Julia has a concrete type

  • Concrete types correspond to computer representations of objects

  • Inquire type info using typeof()

Built-in types

  • Default types are deduced from concrete representations

2.1 ms
Int64
2.9 μs
Float64
1.2 μs
Complex{Float64}
9.8 ms
Irrational{:π}
3.0 μs
Bool
1.2 μs
String
1.4 μs
Array{Float16,1}
12.4 ms
Array{Int64,2}
33.9 ms
  • One can initialize a variable with an explicitely given fixed type. Currently this is possible only in the body of functions and for return values, not in the global context. The content of a do block is implicitely used as a function.

7.2 μs
(i, typeof(i)) = (10, Int8)
(x, typeof(x)) = (Float16(5.0), Float16)
(z, typeof(z)) = (15.0f0 + 3.0f0im, Complex{Float32})
962 ms

Custom types

  • Structs allow to define custom types

4.5 μs
389 μs
Color64r
0.1
g
0.2
b
0.3
3.8 μs
  • Types can be parametrized. This is similar to array types which are parametrized by their element types

3.1 μs
1.2 ms
4.9 ms

Functions, Methods and Multiple Dispatch

  • Functions can have different variants of their implementation depending on the types of parameters passed to them

  • These variants are called methods

  • All methods of a function f can be listed calling methods(f)

  • The act of figuring out which method of a function to call depending on the type of parameters is called multiple dispatch

8.0 μs
14.5 μs
17.5 μs
14.5 μs
"special case Int64, x=3"
4.5 μs
"general case: Bool, x=false"
16.9 μs
"special case Float, Float64, x=3.0"
6.1 ms

Here we defined a generic method which works for any variable passed. In the case of Int64 or Float64 parameters, special cases are handeld by different methods of the same function. The compiler decides which method to call. This approach allows to specialize implemtations dependent on data types, e.g. in order to optimize perfomance.

The methods function can be used to figure out which methods of a function exists.

7.5 μs
# 3 methods for generic function test_dispatch:
33.7 μs

The function/method concept somehow corresponds to C++14 generic lambdas

auto myfunc=[](auto  &y, auto &y)
{
  y=sin(x);
};

is equivalent to

function myfunc!(y,x)
    y=sin(x)
end

Many generic programming approaches possible in C++ also work in Julia,

If not specified otherwise via parameter types, Julia functions are generic: "automatic auto"

6.4 μs

Abstract types

  • Abstract types label concepts which work for a several concrete types without regard to their memory layout etc.

  • All variables with concrete types corresponding to a given abstract type (should) share a common interface

  • A common interface consists of a set of functions with methods working for all types exhibiting this interface

  • The functionality of an abstract type is implicitely characterized by the methods working on it

  • This concept is close to "duck typing": use the "duck test" — "If it walks like a duck and it quacks like a duck, then it must be a duck" — to determine if an object can be used for a particular purpose

  • When trying to force a parmameter to have an abstract type,it

ends up with having a conrete type which is compatible with that abstract type

25.1 μs
(i, typeof(i)) = (10, Int64)
(x, typeof(x)) = (5.0, Float64)
(z, typeof(z)) = (15 + 3im, Complex{Int64})
137 ms

The type tree

  • Types can have subtypes and a supertype

  • Concrete types are the leaves of the resulting type tree

  • Supertypes are necessarily abstract

  • There is only one supertype for every (abstract or concrete) type

  • Abstract types can have several subtypes

8.0 μs
52.3 ms
  • Concrete types have no subtypes

3.1 μs
3.4 μs
Any
3.6 μs
  • "Any" is the root of the type tree and has itself as supertype

3.1 μs
Any
2.7 μs

We can use the AbstractTrees package to walk the type tree. We just need to define what it means to have children for a type.

2.9 μs
18.0 μs
Number
├─ Complex
└─ Real
   ├─ AbstractFloat
   │  ├─ BigFloat
   │  ├─ Float16
   │  ├─ Float32
   │  └─ Float64
   ├─ AbstractIrrational
   │  └─ Irrational
   ├─ Integer
   │  ├─ Bool
   │  ├─ Signed
   │  │  ├─ BigInt
   │  │  ├─ Int128
   │  │  ├─ Int16
   │  │  ├─ Int32
   │  │  ├─ Int64
   │  │  └─ Int8
   │  └─ Unsigned
   │     ├─ UInt128
   │     ├─ UInt16
   │     ├─ UInt32
   │     ├─ UInt64
   │     └─ UInt8
   └─ Rational
8.1 μs

There are operators for testing type relationships

4.9 μs
true
3.6 μs
false
4.0 μs
false
3.0 μs
true
5.7 μs

Abstract types can be used for method dispatch as well

2.3 μs
dispatch2 (generic function with 2 methods)
35.8 μs
"Int64 <:Integer, x=13"
2.6 ms
"Float64 <:AbstractFloat, x=13.0"
3.0 ms

The power of multiple dispatch

  • Multiple dispatch is one of the defining features of Julia

  • Combined with the the hierarchical type system it allows for powerful generic program design

  • New datatypes (different kinds of numbers, differently stored arrays/matrices) work with existing code once they implement the same interface as existent ones.

  • In some respects C++ comes close to it, but for the price of more and less obvious code

6.4 μs