Multiple Dispatch and Types

Julia programs are oriented around "Multiple dispatch", sort of a generalization of "object-oriented programming".

Typical object-oriented syntax:

object.method(arg1, arg2)

Julia way to "spell" this:

method(object, arg1, arg2)

Why is the first argument special?

Typical OO programming corresponds to single dispatch - the type of the first argument determines which method is called. multiple dispatch uses the types of all arguments to determine which method is called.

Example: Binary operators

When doing x * y, the system needs to choose the method to call based on the types of both x and y. In Julia, see all the methods for *:


In [1]:
methods(*)


Out[1]:
115 methods for generic function *:
  • *(x::Bool,y::Bool) at bool.jl:41
  • *{T<:Unsigned}(x::Bool,y::T<:Unsigned) at bool.jl:56
  • *(x::Bool,z::Complex{T<:Real}) at complex.jl:116
  • *{T<:Number}(x::Bool,y::T<:Number) at bool.jl:52
  • *(z::Complex{T<:Real},x::Bool) at complex.jl:117
  • *(y::Number,x::Bool) at bool.jl:58
  • *{T,S}(A::Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)}),B::Union(SubArray{S,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)},DenseArray{S,2})) at linalg/matmul.jl:116
  • *(A::Union(Symmetric{T},Hermitian{T}),B::Union(Symmetric{T},Hermitian{T})) at linalg/symmetric.jl:35
  • *(A::Union(Symmetric{T},Hermitian{T}),B::Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)})) at linalg/symmetric.jl:36
  • *(A::Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)}),B::Union(Symmetric{T},Hermitian{T})) at linalg/symmetric.jl:37
  • *{T<:Union(Complex{Float64},Float64,Complex{Float32},Float32),S<:Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)}),UpLo,IsUnit}(A::Triangular{T<:Union(Complex{Float64},Float64,Complex{Float32},Float32),S<:Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)}),UpLo,IsUnit},B::Triangular{T<:Union(Complex{Float64},Float64,Complex{Float32},Float32),S<:Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)}),UpLo,IsUnit}) at linalg/triangular.jl:23
  • *(A::Tridiagonal{T},B::Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit}) at linalg/triangular.jl:206
  • *{TA,TB,SA<:AbstractArray{T,2},SB<:AbstractArray{T,2},UpLoA,UpLoB,IsUnitA,IsUnitB}(A::Triangular{TA,SA<:AbstractArray{T,2},UpLoA,IsUnitA},B::Triangular{TB,SB<:AbstractArray{T,2},UpLoB,IsUnitB}) at linalg/triangular.jl:209
  • *(Da::Diagonal{T},Db::Diagonal{T}) at linalg/diagonal.jl:53
  • *(A::Union(Bidiagonal{T},Diagonal{T},Tridiagonal{T},Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit},SymTridiagonal{T}),B::Union(Bidiagonal{T},Diagonal{T},Tridiagonal{T},Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit},SymTridiagonal{T})) at linalg/bidiag.jl:114
  • *{T<:Union(Int16,Int8,Int32)}(x::T<:Union(Int16,Int8,Int32),y::T<:Union(Int16,Int8,Int32)) at int.jl:18
  • *{T<:Union(Uint8,Uint16,Uint32)}(x::T<:Union(Uint8,Uint16,Uint32),y::T<:Union(Uint8,Uint16,Uint32)) at int.jl:22
  • *(x::Int64,y::Int64) at int.jl:47
  • *(x::Uint64,y::Uint64) at int.jl:48
  • *(x::Int128,y::Int128) at int.jl:586
  • *(x::Uint128,y::Uint128) at int.jl:587
  • *(x::Float32,y::Float32) at float.jl:123
  • *(x::Float64,y::Float64) at float.jl:124
  • *(z::Complex{T<:Real},w::Complex{T<:Real}) at complex.jl:112
  • *(x::Real,z::Complex{T<:Real}) at complex.jl:118
  • *(z::Complex{T<:Real},x::Real) at complex.jl:119
  • *(x::Rational{T<:Integer},y::Rational{T<:Integer}) at rational.jl:118
  • *(a::Float16,b::Float16) at float16.jl:132
  • *(x::BigInt,y::BigInt) at gmp.jl:195
  • *(a::BigInt,b::BigInt,c::BigInt) at gmp.jl:218
  • *(a::BigInt,b::BigInt,c::BigInt,d::BigInt) at gmp.jl:224
  • *(a::BigInt,b::BigInt,c::BigInt,d::BigInt,e::BigInt) at gmp.jl:231
  • *(x::BigInt,c::Union(Uint64,Uint8,Uint16,Uint32)) at gmp.jl:265
  • *(c::Union(Uint64,Uint8,Uint16,Uint32),x::BigInt) at gmp.jl:269
  • *(x::BigInt,c::Union(Int16,Int8,Int64,Int32)) at gmp.jl:271
  • *(c::Union(Int16,Int8,Int64,Int32),x::BigInt) at gmp.jl:275
  • *(x::BigFloat,y::BigFloat) at mpfr.jl:149
  • *(x::BigFloat,c::Union(Uint64,Uint8,Uint16,Uint32)) at mpfr.jl:156
  • *(c::Union(Uint64,Uint8,Uint16,Uint32),x::BigFloat) at mpfr.jl:160
  • *(x::BigFloat,c::Union(Int16,Int8,Int64,Int32)) at mpfr.jl:164
  • *(c::Union(Int16,Int8,Int64,Int32),x::BigFloat) at mpfr.jl:168
  • *(x::BigFloat,c::Union(Float16,Float64,Float32)) at mpfr.jl:172
  • *(c::Union(Float16,Float64,Float32),x::BigFloat) at mpfr.jl:176
  • *(x::BigFloat,c::BigInt) at mpfr.jl:180
  • *(c::BigInt,x::BigFloat) at mpfr.jl:184
  • *(a::BigFloat,b::BigFloat,c::BigFloat) at mpfr.jl:255
  • *(a::BigFloat,b::BigFloat,c::BigFloat,d::BigFloat) at mpfr.jl:261
  • *(a::BigFloat,b::BigFloat,c::BigFloat,d::BigFloat,e::BigFloat) at mpfr.jl:268
  • *(x::MathConst{sym},y::MathConst{sym}) at constants.jl:23
  • *{T<:Number}(x::T<:Number,y::T<:Number) at promotion.jl:189
  • *(x::Number,y::Number) at promotion.jl:159
  • *{T<:Number}(x::T<:Number,D::Diagonal{T}) at linalg/diagonal.jl:50
  • *(s::Real,p::Vec2) at graphics.jl:64
  • *(s::Real,bb::BoundingBox) at graphics.jl:151
  • *(x::Number) at operators.jl:72
  • *{T<:Union(Complex{Float64},Float64,Complex{Float32},Float32),S}(A::Union(SubArray{T<:Union(Complex{Float64},Float64,Complex{Float32},Float32),2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)},DenseArray{T<:Union(Complex{Float64},Float64,Complex{Float32},Float32),2}),x::Union(SubArray{S,1,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)},DenseArray{S,1})) at linalg/matmul.jl:67
  • *(A::SymTridiagonal{T},B::Number) at linalg/tridiag.jl:59
  • *(A::Tridiagonal{T},B::Number) at linalg/tridiag.jl:249
  • *{T<:Union(Complex{Float64},Float64,Complex{Float32},Float32),S<:Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)}),UpLo,IsUnit}(A::Triangular{T<:Union(Complex{Float64},Float64,Complex{Float32},Float32),S<:Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)}),UpLo,IsUnit},B::Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)},SubArray{T,1,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)},DenseArray{T,1})) at linalg/triangular.jl:24
  • *{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit},x::Number) at linalg/triangular.jl:157
  • *{TvA,TiA}(X::Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit},A::SparseMatrixCSC{TvA,TiA}) at linalg/sparse.jl:129
  • *{T,S<:AbstractArray{T,2},UpLo,IsUnit}(A::Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit},B::Union(AbstractArray{T,1},AbstractArray{T,2})) at linalg/triangular.jl:210
  • *{TA,Tb}(A::Union(QRCompactWYQ{TA},QRPackedQ{TA}),b::Union(DenseArray{Tb,1},SubArray{Tb,1,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)})) at linalg/factorization.jl:277
  • *{TA,TB}(A::Union(QRCompactWYQ{TA},QRPackedQ{TA}),B::Union(SubArray{TB,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)},DenseArray{TB,2})) at linalg/factorization.jl:283
  • *{TA,TQ,N}(A::Union(DenseArray{TA,N},SubArray{TA,N,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)}),Q::Union(QRPackedQ{TQ},QRCompactWYQ{TQ})) at linalg/factorization.jl:345
  • *(W::Woodbury{T},B::Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)},SubArray{T,1,A<:DenseArray{T,N},I<:(Union(Range{Int64},Int64)...,)},DenseArray{T,1})) at linalg/woodbury.jl:70
  • *{T<:Number}(D::Diagonal{T},x::T<:Number) at linalg/diagonal.jl:51
  • *(D::Diagonal{T},V::Array{T,1}) at linalg/diagonal.jl:54
  • *(A::Array{T,2},D::Diagonal{T}) at linalg/diagonal.jl:55
  • *(D::Diagonal{T},A::Array{T,2}) at linalg/diagonal.jl:56
  • *(A::Bidiagonal{T},B::Number) at linalg/bidiag.jl:108
  • *{T}(A::Bidiagonal{T},B::AbstractArray{T,1}) at linalg/bidiag.jl:119
  • *(B::BitArray{2},J::UniformScaling{T<:Number}) at linalg/uniformscaling.jl:68
  • *(S::SparseMatrixCSC{Tv,Ti<:Integer},J::UniformScaling{T<:Number}) at linalg/uniformscaling.jl:70
  • *{T}(G1::Givens{T},G2::Givens{T}) at linalg/givens.jl:197
  • *(G::Givens{T},B::BitArray{2}) at linalg/givens.jl:198
  • *{TBf,TBi}(G::Givens{T},B::SparseMatrixCSC{TBf,TBi}) at linalg/givens.jl:199
  • *(R::Union(Givens{T},Rotation{T}),A::AbstractArray{T,2}) at linalg/givens.jl:200
  • *{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti},B::SparseMatrixCSC{Tv,Ti}) at linalg/sparse.jl:143
  • *{TvA,TiA,TvB,TiB}(A::SparseMatrixCSC{TvA,TiA},B::SparseMatrixCSC{TvB,TiB}) at linalg/sparse.jl:4
  • *{TvA,TiA}(A::SparseMatrixCSC{TvA,TiA},X::BitArray{1}) at linalg/sparse.jl:11
  • *{TvA,TiA}(A::SparseMatrixCSC{TvA,TiA},X::BitArray{2}) at linalg/sparse.jl:109
  • *{TA,S,Tx}(A::SparseMatrixCSC{TA,S},x::AbstractArray{Tx,1}) at linalg/sparse.jl:32
  • *(X::BitArray{1},A::SparseMatrixCSC{Tv,Ti<:Integer}) at linalg/sparse.jl:95
  • *{TvA,TiA,TX}(A::SparseMatrixCSC{TvA,TiA},X::AbstractArray{TX,2}) at linalg/sparse.jl:111
  • *{TvA,TiA}(X::BitArray{2},A::SparseMatrixCSC{TvA,TiA}) at linalg/sparse.jl:126
  • *{TX,TvA,TiA}(X::Tridiagonal{TX},A::SparseMatrixCSC{TvA,TiA}) at linalg/sparse.jl:128
  • *{T<:Number}(x::AbstractArray{T<:Number,N}) at abstractarray.jl:363
  • *(B::Number,A::SymTridiagonal{T}) at linalg/tridiag.jl:60
  • *(B::Number,A::Tridiagonal{T}) at linalg/tridiag.jl:250
  • *{T,S,UpLo,IsUnit}(x::Number,A::Triangular{T,S,UpLo,IsUnit}) at linalg/triangular.jl:167
  • *(B::Number,A::Bidiagonal{T}) at linalg/bidiag.jl:109
  • *(A::Number,B::AbstractArray{T,N}) at abstractarray.jl:367
  • *(A::AbstractArray{T,N},B::Number) at abstractarray.jl:368
  • *(s::String...) at string.jl:76
  • *{T,S<:AbstractArray{T,2},UpLo,IsUnit}(A::AbstractArray{T,2},B::Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit}) at linalg/triangular.jl:211
  • *{TX,TvA,TiA}(X::AbstractArray{TX,2},A::SparseMatrixCSC{TvA,TiA}) at linalg/sparse.jl:131
  • *{T,S}(A::AbstractArray{T,2},x::AbstractArray{S,1}) at linalg/matmul.jl:71
  • *{T1,T2}(X::AbstractArray{T1,1},A::SparseMatrixCSC{T2,Ti<:Integer}) at linalg/sparse.jl:99
  • *(A::AbstractArray{T,1},B::AbstractArray{T,2}) at linalg/matmul.jl:74
  • *(J1::UniformScaling{T<:Number},J2::UniformScaling{T<:Number}) at linalg/uniformscaling.jl:67
  • *(J::UniformScaling{T<:Number},B::BitArray{2}) at linalg/uniformscaling.jl:69
  • *{Tv,Ti}(J::UniformScaling{T<:Number},S::SparseMatrixCSC{Tv,Ti}) at linalg/uniformscaling.jl:71
  • *(A::AbstractArray{T,2},J::UniformScaling{T<:Number}) at linalg/uniformscaling.jl:72
  • *(J::UniformScaling{T<:Number},A::Union(AbstractArray{T,1},AbstractArray{T,2})) at linalg/uniformscaling.jl:73
  • *(x::Number,J::UniformScaling{T<:Number}) at linalg/uniformscaling.jl:75
  • *(J::UniformScaling{T<:Number},x::Number) at linalg/uniformscaling.jl:76
  • *{Tv<:Float64}(A::CholmodSparse{Tv<:Float64,Int32},B::CholmodSparse{Tv<:Float64,Int32}) at linalg/cholmod.jl:496
  • *{Tv<:Float64}(A::CholmodSparse{Tv<:Float64,Int64},B::CholmodSparse{Tv<:Float64,Int64}) at linalg/cholmod.jl:496
  • *{Tv<:Union(Complex{Float64},Float64)}(A::CholmodSparse{Tv<:Union(Complex{Float64},Float64),Ti<:Union(Int64,Int32)},B::CholmodDense{Tv<:Union(Complex{Float64},Float64)}) at linalg/cholmod.jl:870
  • *{Tv<:Union(Complex{Float64},Float64)}(A::CholmodSparse{Tv<:Union(Complex{Float64},Float64),Ti<:Union(Int64,Int32)},B::Union(Array{Tv<:Union(Complex{Float64},Float64),1},Array{Tv<:Union(Complex{Float64},Float64),2})) at linalg/cholmod.jl:871
  • *(p::Vec2,s::Real) at graphics.jl:62
  • *(bb::BoundingBox,s::Real) at graphics.jl:147
  • *(a,b,c) at operators.jl:82
  • *(a,b,c,xs...) at operators.jl:83

Methods don't "belong to" any given type

They aren't part of the Type's definition and we can add more methods to any type at any point.


In [2]:
# string concatenation
"hello" * "world"


Out[2]:
"helloworld"

In [3]:
"hello" + "world"


`+` has no method matching +(::ASCIIString, ::ASCIIString)
while loading In[3], in expression starting on line 1

In [4]:
# suppose you really like "+" for concatenation
+(a::String, b::String) = a * " " * b


Out[4]:
+ (generic function with 118 methods)

In [5]:
"hello" + "world"


Out[5]:
"hello world"

In [6]:
# now that we've defined +, the sum function just works (different than C++)
sum(["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog."])


Out[6]:
"The quick brown fox jumps over the lazy dog."

Type declarations are "filters", not for performance

In Python you might do something like this:

def write_my_data(name_or_obj, data):
    """write data to an open file or the supplied filename"""

    if isinstance(name_or_obj, str):
        f = open(name_or_obj, 'w')
    else:
        f = name_or_obj

    # ... write data to f

    if isinstance(name_or_obj, str):
        f.close()

In Julia:


In [ ]:
function write_my_data(f::IOStream, data)
    # write data to f
end

function write_my_data(fname::String, data)
    f = open(fname, 'w')
    write_my_data(f)
    close(f)
end

User-defined Types

In Julia, Types are the equivalent of classes in Python.

A user-defined "composite type" is a collection of data. Unlike in Python, types do not "own" methods (functions internal to the type).

Rather, methods are defined separately, and are characterised by the types of all of their arguments; this is known as multiple dispatch. (Dispatch is the process of choosing which "version" of a given function to execute.)


In [ ]:
# Create a type by specifying the data it contains.
immutable Vector2D
    x::Float64
    y::Float64
end

In [ ]:
# Create two instances
v = Vector2D(3, 4)
w = Vector2D(5, 6)

In [ ]:
v + w

In [ ]:
+(v::Vector2D, w::Vector2D) = Vector2D(v.x+w.x, v.y+w.y)

In [ ]:
v + w

In [ ]:
*(v::Vector2D, α::Number) = Vector2D(v.x*α, v.y*α)

In [ ]:
v * 3.5

In [ ]:
# The equivalent of the Python __repr__ method for an object is to extend the show method

import Base.show

show(io::IO, v::Vector2D) = print(io, "[$(v.x), $(v.y)]")

In [ ]:
v

Parameterized types


In [ ]:
immutable TVector2D{T <: Real}
    x::T
    y::T
end

T is a type parameter. The expression T <: Real means that T must be a subtype of the abstract type Real.


In [ ]:
v = TVector2D(3., 4.)

In [ ]:
w = TVector2D(1, 2)

In [ ]:
show{T}(io::IO, v::TVector2D{T}) = print(io, "[$(v.x), $(v.y)]")

In [ ]:
v

In [ ]:
# Define an "outer constructor" - another way to construct a Vector2D.
TVector2D{T}(x::T) = Vector2D(x, x)

In [ ]:
TVector2D(3.0)