Let's play rock paper scissors:
abstract type Shape end
struct Rock <: Shape end
struct Paper <: Shape end
struct Scissors <: Shape end
play(::Paper, ::Rock) = "Paper wins"
play(::Paper, ::Scissors) = "Scissors wins"
play(::Rock, ::Scissors) = "Rock wins"
play(::T, ::T) where {T<: Shape} = "Tie, try again"
play(a::Shape, b::Shape) = play(b, a) # Commutativity
play (generic function with 5 methods)
julia> play(Paper(), Scissors())
Scissors wins
julia> play(Rock(), Paper())
Paper wins
julia> play(Scissors(), Scissors())
Tie, try again
We've got a lot of new stuff going on here! First we have the ::Paper
statement. This syntax exists for when you don't care about the value of an argument, and only its type.
Second we have the syntax:
play(::T, ::T) where {T<: Shape} = "Tie, try again"
Much like parametric structs this is a parametric method with a slot named T
which must be a subtype of Shape
. Notice that both arguments have the same type T
Next we have our first real look at the difference between a Function
and a Method
, and how to perform specialization:
julia> play
Main.__FRANKLIN_1181134.play
julia> methods(play)
# 5 methods for generic function "play" from Main.__FRANKLIN_1181134:
[1] play(::Main.__FRANKLIN_1181134.Rock, ::Main.__FRANKLIN_1181134.Scissors)
@ string:8
[2] play(::Main.__FRANKLIN_1181134.Paper, ::Main.__FRANKLIN_1181134.Scissors)
@ string:7
[3] play(::Main.__FRANKLIN_1181134.Paper, ::Main.__FRANKLIN_1181134.Rock)
@ string:6
[4] play(::T, ::T) where T<:Main.__FRANKLIN_1181134.Shape
@ string:9
[5] play(a::Main.__FRANKLIN_1181134.Shape, b::Main.__FRANKLIN_1181134.Shape)
@ string:10
So what's going on here?
A function is really just a name for a table of 1 or more methods. When you call a function in Julia the arguments you provided have concrete types, for instance when I call:
julia> play(Paper(), Rock())
Paper wins
Julia will go searching for a function named play
with most specific signature that matches my arguments. Instead of (Paper(), Rock())
as the input to methods
we pass the type of the argument tuple:
julia> dump(methods(play, Tuple{Paper, Rock}))
Base.MethodList
ms: Array{Method}((1,))
1: Method
name: Symbol play
module: Module Main.__FRANKLIN_1181134
file: Symbol string
line: Int32 6
primary_world: UInt64 0x00000000000068df
deleted_world: UInt64 0xffffffffffffffff
sig: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Paper, Main.__FRANKLIN_1181134.Rock} <: Any
specializations: Core.MethodInstance
def: Method#= circular reference @-2 =#
specTypes: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Paper, Main.__FRANKLIN_1181134.Rock} <: Any
sparam_vals: empty SimpleVector
uninferred: #undef
backedges: Array{Any}((1,))
1: Core.MethodInstance
def: Method
name: Symbol play
module: Module Main.__FRANKLIN_1181134
file: Symbol string
line: Int32 10
primary_world: UInt64 0x00000000000068e3
deleted_world: UInt64 0xffffffffffffffff
sig: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Shape, Main.__FRANKLIN_1181134.Shape} <: Any
specializations: Core.MethodInstance#= circular reference @-2 =#
speckeyset: Memory{Any}
length: Int64 0
ptr: Ptr{Nothing} @0x00007fca21b0aea0
slot_syms: String "#self#\0a\0b\0"
external_mt: #undef
source: String "\0\0\0\xff\xff\x03\0\0\0\0\b\b\x16\xbe\xe2.\x1e\x11\0\x05\x03\0\x05\x02\x007\x03\x01\xbe\x16\xbe\xee\0\0\0\0\0\0\0\0A\x16\xbd\xe2/#\x11\0\x81\xb1\xa7A\x01\0\0"
unspecialized: #undef
generator: #undef
roots: Array{Any}((2,))
1: Symbol play
2: String "Paper wins"
root_blocks: #undef
nroots_sysimg: Int32 0
ccallable: #undef
invokes: Nothing nothing
recursion_relation: #undef
nargs: Int32 3
called: Int32 0
nospecialize: Int32 0
nkw: Int32 0
isva: Bool false
is_for_opaque_closure: Bool false
nospecializeinfer: Bool false
constprop: UInt8 0x00
max_varargs: UInt8 0xff
purity: UInt16 0x0000
specTypes: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Rock, Main.__FRANKLIN_1181134.Paper} <: Any
sparam_vals: empty SimpleVector
uninferred: #undef
backedges: #undef
cache: Core.CodeInstance
def: Core.MethodInstance#= circular reference @-2 =#
owner: Nothing nothing
next: #undef
min_world: UInt64 0x00000000000068e3
max_world: UInt64 0xffffffffffffffff
rettype: String <: AbstractString
exctype: Union{}
rettype_const: String "Paper wins"
inferred: String "\x01\0\0\n\0\x03\0\0\0\0\b\b\x16\xbd\xe27\x11\x01\x16\xbd\xe2B\x16\xbd\xee\xa0\x02\0\0A\x16\xbe'\0//#\x11\0\x81\xb1\xa7/#\x11\0\x81\xad\xa8A\0\0"
ipo_purity_bits: UInt32 0x000040e0
purity_bits: UInt32 0x000040e0
analysis_results: Core.Compiler.AnalysisResults
result: Nothing nothing
next: #undef
specsigflags: Bool true
precompile: Bool true
relocatability: UInt8 0x00
invoke: Ptr{Nothing} @0x00007fca0e18b9a0
specptr: Ptr{Nothing} @0x00007fca0e18b980
inInference: Bool false
cache_with_orig: Bool false
precompiled: Bool false
cache: Core.CodeInstance
def: Core.MethodInstance#= circular reference @-2 =#
owner: Nothing nothing
next: #undef
min_world: UInt64 0x0000000000000001
max_world: UInt64 0xffffffffffffffff
rettype: String <: AbstractString
exctype: Union{}
rettype_const: String "Paper wins"
inferred: String "\x01\0\0\n\0\x03\0\0\0\0\0\0\x16\xbd\xe27\x11\0\x16\xbd\xe2B\x16\xbd\xee\xa0\x02\0\0A\x16\xbd'\0//#\x11\x01\x81\xad\xa7A\0\0"
ipo_purity_bits: UInt32 0x000040e0
purity_bits: UInt32 0x000040e0
analysis_results: Core.Compiler.AnalysisResults
result: Nothing nothing
next: #undef
specsigflags: Bool true
precompile: Bool true
relocatability: UInt8 0x00
invoke: Ptr{Nothing} @0x00007fca0e1957c0
specptr: Ptr{Nothing} @0x00007fca0e1957a0
inInference: Bool false
cache_with_orig: Bool false
precompiled: Bool false
speckeyset: Memory{Any}
length: Int64 0
ptr: Ptr{Nothing} @0x00007fca21b0aea0
slot_syms: String "#self#\0#unused#\0#unused#\0"
external_mt: #undef
source: String "\0\0\0\xff\xff\x03\0\0\0\0\0\0\x16\xbd\xe27\x11\0\xbd\x16\xbd\xee\0\0\0\0A\x16\xbd\xe2/#\x11\x01\x81\xad\xa7A\0\0"
unspecialized: #undef
generator: #undef
roots: Array{Any}((2,))
1: String "Paper wins"
2: Symbol play
root_blocks: #undef
nroots_sysimg: Int32 0
ccallable: #undef
invokes: Nothing nothing
recursion_relation: #undef
nargs: Int32 3
called: Int32 0
nospecialize: Int32 0
nkw: Int32 0
isva: Bool false
is_for_opaque_closure: Bool false
nospecializeinfer: Bool false
constprop: UInt8 0x00
max_varargs: UInt8 0xff
purity: UInt16 0x0000
mt: Core.MethodTable
name: Symbol play
defs: Core.TypeMapEntry
next: Core.TypeMapEntry
next: Core.TypeMapEntry
next: Core.TypeMapEntry
next: Core.TypeMapEntry
next: Nothing nothing
sig: UnionAll
var: TypeVar
body: Tuple{typeof(Main.__FRANKLIN_1181134.play), T<:Main.__FRANKLIN_1181134.Shape, T<:Main.__FRANKLIN_1181134.Shape} <: Any
simplesig: Nothing nothing
guardsigs: empty SimpleVector
min_world: UInt64 0x00000000000068e2
max_world: UInt64 0xffffffffffffffff
func: Method
name: Symbol play
module: Module Main.__FRANKLIN_1181134
file: Symbol string
line: Int32 9
primary_world: UInt64 0x00000000000068e2
deleted_world: UInt64 0xffffffffffffffff
sig: UnionAll
specializations: Core.MethodInstance
speckeyset: Memory{Any}
slot_syms: String "#self#\0#unused#\0#unused#\0"
external_mt: #undef
source: String "\0\0\0\xff\xff\x03\0\0\0\0\0\0\x16\xbd\xe27\x11\0\xbd\x16\xbd\xee\0\0\0\0A\x16\xbd\xe2/#\x11\x01\x81\xb0\xa7A\0\0"
unspecialized: #undef
generator: #undef
roots: Array{Any}((2,))
root_blocks: #undef
nroots_sysimg: Int32 0
ccallable: #undef
invokes: Nothing nothing
recursion_relation: #undef
nargs: Int32 3
called: Int32 0
nospecialize: Int32 0
nkw: Int32 0
isva: Bool false
is_for_opaque_closure: Bool false
nospecializeinfer: Bool false
constprop: UInt8 0x00
max_varargs: UInt8 0xff
purity: UInt16 0x0000
isleafsig: Bool false
issimplesig: Bool false
va: Bool false
sig: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Shape, Main.__FRANKLIN_1181134.Shape} <: Any
simplesig: Nothing nothing
guardsigs: empty SimpleVector
min_world: UInt64 0x00000000000068e3
max_world: UInt64 0xffffffffffffffff
func: Method
name: Symbol play
module: Module Main.__FRANKLIN_1181134
file: Symbol string
line: Int32 10
primary_world: UInt64 0x00000000000068e3
deleted_world: UInt64 0xffffffffffffffff
sig: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Shape, Main.__FRANKLIN_1181134.Shape} <: Any
specializations: Core.MethodInstance
def: Method
specTypes: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Rock, Main.__FRANKLIN_1181134.Paper} <: Any
sparam_vals: empty SimpleVector
uninferred: #undef
backedges: #undef
cache: Core.CodeInstance
inInference: Bool false
cache_with_orig: Bool false
precompiled: Bool false
speckeyset: Memory{Any}
length: Int64 0
ptr: Ptr{Nothing} @0x00007fca21b0aea0
slot_syms: String "#self#\0a\0b\0"
external_mt: #undef
source: String "\0\0\0\xff\xff\x03\0\0\0\0\b\b\x16\xbe\xe2.\x1e\x11\0\x05\x03\0\x05\x02\x007\x03\x01\xbe\x16\xbe\xee\0\0\0\0\0\0\0\0A\x16\xbd\xe2/#\x11\0\x81\xb1\xa7A\x01\0\0"
unspecialized: #undef
generator: #undef
roots: Array{Any}((2,))
1: Symbol play
2: String "Paper wins"
root_blocks: #undef
nroots_sysimg: Int32 0
ccallable: #undef
invokes: Nothing nothing
recursion_relation: #undef
nargs: Int32 3
called: Int32 0
nospecialize: Int32 0
nkw: Int32 0
isva: Bool false
is_for_opaque_closure: Bool false
nospecializeinfer: Bool false
constprop: UInt8 0x00
max_varargs: UInt8 0xff
purity: UInt16 0x0000
isleafsig: Bool false
issimplesig: Bool false
va: Bool false
sig: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Paper, Main.__FRANKLIN_1181134.Rock} <: Any
simplesig: Nothing nothing
guardsigs: empty SimpleVector
min_world: UInt64 0x00000000000068df
max_world: UInt64 0xffffffffffffffff
func: Method
name: Symbol play
module: Module Main.__FRANKLIN_1181134
file: Symbol string
line: Int32 6
primary_world: UInt64 0x00000000000068df
deleted_world: UInt64 0xffffffffffffffff
sig: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Paper, Main.__FRANKLIN_1181134.Rock} <: Any
specializations: Core.MethodInstance
def: Method#= circular reference @-2 =#
specTypes: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Paper, Main.__FRANKLIN_1181134.Rock} <: Any
sparam_vals: empty SimpleVector
uninferred: #undef
backedges: Array{Any}((1,))
1: Core.MethodInstance
cache: Core.CodeInstance
def: Core.MethodInstance
owner: Nothing nothing
next: #undef
min_world: UInt64 0x0000000000000001
max_world: UInt64 0xffffffffffffffff
rettype: String <: AbstractString
exctype: Union{}
rettype_const: String "Paper wins"
inferred: String "\x01\0\0\n\0\x03\0\0\0\0\0\0\x16\xbd\xe27\x11\0\x16\xbd\xe2B\x16\xbd\xee\xa0\x02\0\0A\x16\xbd'\0//#\x11\x01\x81\xad\xa7A\0\0"
ipo_purity_bits: UInt32 0x000040e0
purity_bits: UInt32 0x000040e0
analysis_results: Core.Compiler.AnalysisResults
specsigflags: Bool true
precompile: Bool true
relocatability: UInt8 0x00
invoke: Ptr{Nothing} @0x00007fca0e1957c0
specptr: Ptr{Nothing} @0x00007fca0e1957a0
inInference: Bool false
cache_with_orig: Bool false
precompiled: Bool false
speckeyset: Memory{Any}
length: Int64 0
ptr: Ptr{Nothing} @0x00007fca21b0aea0
slot_syms: String "#self#\0#unused#\0#unused#\0"
external_mt: #undef
source: String "\0\0\0\xff\xff\x03\0\0\0\0\0\0\x16\xbd\xe27\x11\0\xbd\x16\xbd\xee\0\0\0\0A\x16\xbd\xe2/#\x11\x01\x81\xad\xa7A\0\0"
unspecialized: #undef
generator: #undef
roots: Array{Any}((2,))
1: String "Paper wins"
2: Symbol play
root_blocks: #undef
nroots_sysimg: Int32 0
ccallable: #undef
invokes: Nothing nothing
recursion_relation: #undef
nargs: Int32 3
called: Int32 0
nospecialize: Int32 0
nkw: Int32 0
isva: Bool false
is_for_opaque_closure: Bool false
nospecializeinfer: Bool false
constprop: UInt8 0x00
max_varargs: UInt8 0xff
purity: UInt16 0x0000
isleafsig: Bool true
issimplesig: Bool true
va: Bool false
sig: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Paper, Main.__FRANKLIN_1181134.Scissors} <: Any
simplesig: Nothing nothing
guardsigs: empty SimpleVector
min_world: UInt64 0x00000000000068e0
max_world: UInt64 0xffffffffffffffff
func: Method
name: Symbol play
module: Module Main.__FRANKLIN_1181134
file: Symbol string
line: Int32 7
primary_world: UInt64 0x00000000000068e0
deleted_world: UInt64 0xffffffffffffffff
sig: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Paper, Main.__FRANKLIN_1181134.Scissors} <: Any
specializations: Core.MethodInstance
def: Method#= circular reference @-2 =#
specTypes: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Paper, Main.__FRANKLIN_1181134.Scissors} <: Any
sparam_vals: empty SimpleVector
uninferred: #undef
backedges: #undef
cache: Core.CodeInstance
def: Core.MethodInstance#= circular reference @-2 =#
owner: Nothing nothing
next: #undef
min_world: UInt64 0x0000000000000001
max_world: UInt64 0xffffffffffffffff
rettype: String <: AbstractString
exctype: Union{}
rettype_const: String "Scissors wins"
inferred: String "\x01\0\0\n\0\x03\0\0\0\0\0\0\x16\xbd\xe27\x11\0\x16\xbd\xe2B\x16\xbd\xee\xa0\x02\0\0A\x16\xbd'\0//#\x11\x01\x81\xae\xa7A\0\0"
ipo_purity_bits: UInt32 0x000040e0
purity_bits: UInt32 0x000040e0
analysis_results: Core.Compiler.AnalysisResults
result: Nothing nothing
next: #undef
specsigflags: Bool true
precompile: Bool true
relocatability: UInt8 0x00
invoke: Ptr{Nothing} @0x00007fca0e189d30
specptr: Ptr{Nothing} @0x00007fca0e189d10
inInference: Bool false
cache_with_orig: Bool false
precompiled: Bool false
speckeyset: Memory{Any}
length: Int64 0
ptr: Ptr{Nothing} @0x00007fca21b0aea0
slot_syms: String "#self#\0#unused#\0#unused#\0"
external_mt: #undef
source: String "\0\0\0\xff\xff\x03\0\0\0\0\0\0\x16\xbd\xe27\x11\0\xbd\x16\xbd\xee\0\0\0\0A\x16\xbd\xe2/#\x11\x01\x81\xae\xa7A\0\0"
unspecialized: #undef
generator: #undef
roots: Array{Any}((2,))
1: String "Scissors wins"
2: Symbol play
root_blocks: #undef
nroots_sysimg: Int32 0
ccallable: #undef
invokes: Nothing nothing
recursion_relation: #undef
nargs: Int32 3
called: Int32 0
nospecialize: Int32 0
nkw: Int32 0
isva: Bool false
is_for_opaque_closure: Bool false
nospecializeinfer: Bool false
constprop: UInt8 0x00
max_varargs: UInt8 0xff
purity: UInt16 0x0000
isleafsig: Bool true
issimplesig: Bool true
va: Bool false
sig: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Rock, Main.__FRANKLIN_1181134.Scissors} <: Any
simplesig: Nothing nothing
guardsigs: empty SimpleVector
min_world: UInt64 0x00000000000068e1
max_world: UInt64 0xffffffffffffffff
func: Method
name: Symbol play
module: Module Main.__FRANKLIN_1181134
file: Symbol string
line: Int32 8
primary_world: UInt64 0x00000000000068e1
deleted_world: UInt64 0xffffffffffffffff
sig: Tuple{typeof(Main.__FRANKLIN_1181134.play), Main.__FRANKLIN_1181134.Rock, Main.__FRANKLIN_1181134.Scissors} <: Any
specializations: empty SimpleVector
speckeyset: Memory{Any}
length: Int64 0
ptr: Ptr{Nothing} @0x00007fca21b0aea0
slot_syms: String "#self#\0#unused#\0#unused#\0"
external_mt: #undef
source: String "\0\0\0\xff\xff\x03\0\0\0\0\0\0\x16\xbd\xe27\x11\0\xbd\x16\xbd\xee\0\0\0\0A\x16\xbd\xe2/#\x11\x01\x81\xaf\xa7A\0\0"
unspecialized: #undef
generator: #undef
roots: Array{Any}((2,))
1: String "Rock wins"
2: Symbol play
root_blocks: #undef
nroots_sysimg: Int32 0
ccallable: #undef
invokes: Nothing nothing
recursion_relation: #undef
nargs: Int32 3
called: Int32 0
nospecialize: Int32 0
nkw: Int32 0
isva: Bool false
is_for_opaque_closure: Bool false
nospecializeinfer: Bool false
constprop: UInt8 0x00
max_varargs: UInt8 0xff
purity: UInt16 0x0000
isleafsig: Bool true
issimplesig: Bool true
va: Bool false
leafcache: Memory{Any}
length: Int64 32
ptr: Ptr{Nothing} @0x00007fc9ea42af60
cache: Nothing nothing
max_args: Int64 3
module: Module Main.__FRANKLIN_1181134
backedges: #undef
: Int64 0
: Int64 0
offs: UInt8 0x01
: UInt8 0x00
🤯
Important - Until you are spelunking through the Julia compiler you never need to look at this But it's still neat to peek behind the curtain
What does that even mean? We're going to borrow very liberally from the wonderful Matthijs Cox who also gave us our opening little comic.
Let's let's take a function f
and a really restricted set of types: Float64, Int, String
and visualize what dispatch really looks like
When f
takes no arguments, the method we select is clear. But you'll notice that each argument we add squares the number of possible methods we could write.
Important - Abstract types, Unions, and parametric methods exist solely to help cover different parts of these spaces (which are often much much larger). Otherwise the parent type has nothing to do with the child type.
f(::Any, ::Any) = println("Any & Any")
f(::Int64, ::Int64) = println("Int & Int")
f (generic function with 2 methods)
julia> f(:🐷, "Oink oink!")
Any & Any
julia> f(1, 2)
Int & Int
We have overlapping dispatches now, but the most specific one wins
f(::Any, ::String) = println("Any & String")
f(::String, ::Any) = println("String & Any")
f (generic function with 4 methods)
julia> f("This is fine", 5)
String & Any
julia> f(5, "So is this!")
Any & String
julia> f("Now", "What?!")
ERROR: MethodError: f(::String, ::String) is ambiguous.
Candidates:
f(::String, ::Any)
@ [Franklin]:2
f(::Any, ::String)
@ [Franklin]:1
Possible fix, define
f(::String, ::String)
Stacktrace:
We have to fix our ambiguity with:
f(::String, ::String) = println("Phew, crisis averted")
f (generic function with 5 methods)