Skip to content

Instantly share code, notes, and snippets.

@0x0dea
Created March 23, 2015 20:29
Show Gist options
  • Save 0x0dea/ff92b5bc4887d1d04f3a to your computer and use it in GitHub Desktop.
Save 0x0dea/ff92b5bc4887d1d04f3a to your computer and use it in GitHub Desktop.
"Real" method overloading in Ruby!
module Ova
# autovivifying map from [class][method][signature] to Method
@@Ova = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
# Wrap #respond_to? for parity with Class#===.
Responder = Struct.new(:method) do
def === obj
obj.respond_to?(method)
end
end
Sum = Struct.new(:types) do
def === obj
types.any? { |t| t === obj }
end
end
def self.signature imeth
iseq = RubyVM::InstructionSequence.of(imeth).to_a
insns = iseq.last
info = iseq.grep(Hash).reduce(:merge)
[Object] * info[:lead_num].to_i +
info[:opt].to_a.each_cons(2).map do |a, b|
from, to = insns.index(a), insns.index(b)
insns[from...to].grep(Array).reduce([]) do |stack, (op, arg)|
stack << case op
when :getconstant # classes
const_get(arg)
when :putobject # symbols
Responder.new(arg)
when :newarray
[stack.pop]
when :duparray # single-symbol arrays (?)
[Responder.new(arg[0])]
when :opt_send_without_block # alternation (|)
Sum.new(stack.pop(2))
else # "no-op"
stack.pop
end
end.pop
end +
[Object] * info[:post_num].to_i
end
def self.dispatch mname
proc do |*args, &blk|
matched, imeth = @@Ova[self.class][mname].find do |sig, _|
sig.size == args.size && sig.zip(args).all? do |match, obj|
obj.all? { |o| match[0] === o } rescue match === obj
end
end
if matched
imeth.bind(self).call(*args, &blk)
else
raise ArgumentError,
"#{self.class}##{mname} of " +
"(#{args.map(&:class) * ', '})" +
" not implemented",
caller
end
end
end
def method_added mname
return if caller[1]['method_added']
imeth = instance_method(mname)
@@Ova[self][mname][Ova.signature(imeth)] = imeth
return if @@Ova[self][mname].size == 1
define_method(mname, Ova.dispatch(mname))
end
end
class Foo
extend Ova
def bar a, b = String, c = [Numeric], d = :to_h | Hash | :keys, e
'This is just silly.'
end
def bar a = String
bar a.to_i
end
def bar a = Numeric
a * 2
end
def bar a = [Numeric]
a.map(&:to_s)
end
end
class Bar
def keys
end
end
foo = Foo.new
p foo.bar 21
p foo.bar '21'
p foo.bar [1, 2, 3]
p foo.bar -> {}, '?!', [1, 2, 3], Bar.new, ObjectSpace
p foo.bar :x, 'y'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment