Double Dispatch
From Plasmaworks
Contents |
Single Dispatch
Slag, like most other object-oriented languages, is single dispatch. This means that when a method is called, the exact variation of the method can vary dynamically depending on the type of the object it's called on but not on the type of the arguments (a single point of flexibility).
Single Dispatch example
# Thanks to single dispatch, the printlns print the correct
# monster name instead of just printing "Monster", even though
# the variables m1 and m2 are of type Monster.
class Test
method init:
local Monster m1 = Ghost()
local Monster m2 = Goblin()
println( m1.to_String ) # prints: Ghost
println( m2.to_String ) # prints: Goblin
endClass
class Monster()
method to_String.String:
return "Monster"
endClass
class Ghost : Monster
method to_String.String:
return "Ghost"
endClass
class Goblin : Monster
method to_String.String:
return "Goblin"
endClass
Where Single Dispatch fails
This example shows where double dispatch would be handy. Note that only the reference type (and not the true object type) of arguments is considered when deciding which method to call.
class Test
method init:
local Monster m1 = Ghost()
local Monster m2 = Goblin()
poke(m1) # prints: "poking Monster"
poke(m2) # prints: "poking Monster"
method poke( Monster m ):
println( "poking Monster" )
method poke( Ghost g ):
println( "poking Ghost" )
method poke( Goblin g ):
println( "poking Goblin" )
endClass
class Monster();
class Ghost : Monster;
class Goblin : Monster;
Double Dispatch
Double dispatch is a mechanism where we force the compiler to consider both the type of the object the method is called on and the true type of the argument sent to the method. Double dispatch is a form of multidispatch that operates on two axes of flexibility (the object and one argument).
General approach
Here is how you would achieve double dispatch in Slag using a technique that works in most languages:
# Classic double dispatch
class Test
method init:
local Monster m1 = Ghost()
local Monster m2 = Goblin()
m1.dispatch_poke(this) # prints: "poking Ghost"
m2.dispatch_poke(this) # prints: "poking Goblin"
method poke( Monster m ):
println( "poking Monster" )
method poke( Ghost g ):
println( "poking Ghost" )
method poke( Goblin g ):
println( "poking Goblin" )
endClass
class Monster()
METHODS
method dispatch_poke( Test test ):
# If we don't override this method, "this" is always type Monster
test.poke(this)
endClass
class Ghost : Monster
method dispatch_poke( Test test ):
test.poke(this)
endClass
class Goblin : Monster
method dispatch_poke( Test test ):
test.poke(this)
endClass
Double Dispatch with "propagated" methods
If you give a Slag method the "propagated" qualifier, the compiler will insert a new copy of the method into each class that inherits the method as if you had typed it in yourself. Our previous example becomes a bit smaller:
# Double dispatch with propagated dispatch methods.
class Test
method init:
local Monster m1 = Ghost()
local Monster m2 = Goblin()
m1.dispatch_poke(this) # prints: "poking Ghost"
m2.dispatch_poke(this) # prints: "poking Goblin"
method poke( Monster m ):
println( "poking Monster" )
method poke( Ghost g ):
println( "poking Ghost" )
method poke( Goblin g ):
println( "poking Goblin" )
endClass
class Monster()
METHODS
propagated method dispatch_poke( Test test ):
test.poke(this)
endClass
class Ghost : Monster;
class Goblin : Monster;