Skip to content

Instantly share code, notes, and snippets.

@alandipert
Created August 9, 2012 20:10
Show Gist options
  • Save alandipert/3307658 to your computer and use it in GitHub Desktop.
Save alandipert/3307658 to your computer and use it in GitHub Desktop.
Multimethods in Ruby
class Multimethod
attr_accessor :dispatch, :methods, :heirarchy, :default_method
class NoMatchingMethodError < StandardError
end
def initialize(&dispatch)
@dispatch = dispatch
@methods = []
@heirarchy = {}
end
def add_method(dispatch_value, &method)
if (dispatch_value == :default)
@default_method = method
else
@methods << [dispatch_value, method]
end
end
def derive(from,to)
# TODO
end
def invoke(*args)
dispatch_value = @dispatch[*args]
@methods.each do |(val, meth)|
if (val == dispatch_value)
return meth[*args]
end
end
return @default_method[*args] if @default_method
raise NoMatchingMethodError, "No method found for value #{dispatch_value}."
end
end
m = Multimethod.new(&:class)
m.add_method Array do |arr|
"I'm an array with #{arr.count} things in it"
end
m.add_method String do |s|
"I'm a string that uppercased is: #{s.upcase}"
end
m.add_method :default do |obj|
"I'm a #{obj.class}, which is dumb"
end
puts m.invoke([1,2,3])
puts m.invoke("some string")
puts m.invoke(123)
####################
class Hero < String; end
class Monster < String; end
fight = Multimethod.new do |*players|
players.map(&:class)
end
fight.add_method [Hero, Hero] do |hero1, hero2|
"#{hero1} high-fives #{hero2}."
end
fight.add_method [Monster, Hero] do |monster, hero|
"#{monster} eats #{hero}."
end
fight.add_method [Hero, Monster] do |hero, monster|
"#{hero} slays #{monster}."
end
fight.add_method [Monster, Monster] do |monster1, monster2|
"#{monster1} plots with #{monster2}."
end
puts fight.invoke(Hero.new("Thor"), Hero.new("Batman"))
puts fight.invoke(Monster.new("Nessy"), Hero.new("Wonderwoman"))
puts fight.invoke(Monster.new("Hitler"), Monster.new("Mussolini"))
# Later on...
class Dragon < String; end
# This doesn't work yet:
# puts fight.invoke(Dragon.new("Puff"), Hero.new("Harry Potter"))
# -> defmulti.rb:34:in `invoke': No method found for value [Dragon, Hero]
# Make it work:
fight.add_method [Dragon, Hero] do |dragon, hero|
"#{dragon} toasts #{hero}."
end
puts fight.invoke(Dragon.new("Puff"), Hero.new("Harry Potter"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment