The Promised Land: Multiple Dispatch

Rock Paper Scissors

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:

Functions and Methods

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

.

The most specific signature??

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

The Cube
The Cube

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.

Signature Coverage
Signature Coverage

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
Signature Overlap
Signature Overlap

We have overlapping dispatches now, but the most specific one wins

Ambiguity 😱

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:
Ambiguity!
Ambiguity!

We have to fix our ambiguity with:

f(::String, ::String) = println("Phew, crisis averted")
f (generic function with 5 methods)
Dispatches all put together
Dispatches all put together
CC BY-SA 4.0 Raye Skye Kimmerer. Last modified: July 14, 2025.
Website built with Franklin.jl and the Julia programming language.