module Detach
The Detach
mixin provides method dispatch according to argument types. Method definitions are separated by name and signatue, allowing for C++ or Java style overloading.
Example:
class Bar include Detach taking['String','String'] def foo(a,b) a.upcase + b.upcase end taking['Integer','Integer'] def foo(a=42,b) a * b end taking['Object','String'] def foo(a,*b) b.map {|s| s.upcase + a.to_s}.join end end
Public Class Methods
included(base)
click to toggle source
Extends the base class with the module Detach::Types
.
# File lib/detach.rb, line 23 def self.included(base) base.extend(Types) end
Public Instance Methods
method_missing(name, *args, &block)
click to toggle source
Provides run-time method lookup according to the types of the args.
All methods matching the name are scored according to both arity and type. Varargs and default values are interpolated with actual values. Predefined classes are compared to actual classes using equality and inheritence checks.
# File lib/detach.rb, line 31 def method_missing(name, *args, &block) obj_method_missing = -> { BasicObject.instance_method(:method_missing).bind(self).call(name, *args, &block) } # some early returns are necessary when Detach has been mixed into # the class of object 'main' case name when :to_ary return obj_method_missing.call when :taking return Types.instance_method(:taking).bind(self.class).call if self.class == Object end (score,best) = (public_methods+protected_methods+private_methods).grep(/^#{Regexp.escape(name)}\(/).collect {|candidate| # extract paramters params = /\((.*)\)/.match(candidate.to_s)[1].scan(/(\w+)-([\w:\)]+)/).collect {|s,t| [s.to_sym, t.split(/::/).inject(Kernel) {|m,c| m = m.const_get(c)}] } # form the list of all required argument classes ctypes = params.values_at(*params.each_index.select {|i| params[i].first == :req}).map(&:last) nreq = ctypes.size # NOTE: ruby only allows a single *args, or a list of a=1, b=2--not both together-- # only one of the following will execute # (A) insert any optional argument classes for as many extra are present params.each_index.select {|i| params[i].first == :opt}.each {|i| ctypes.insert(i, params[i].last) if args.size > ctypes.size } # (B) insert the remaining arguments by exploding the appropriate class by the number extra params.each_index.select {|i| params[i].first == :rest}.each {|i| ctypes.insert(i, *([params[i].last] * (args.size - ctypes.size))) if args.size > ctypes.size } # now score the given args by comparing their actual classes to the predefined classes if args.empty? and ctypes.empty? score = 1 elsif ctypes.size == args.size score = args.map(&:class).zip(ctypes).inject(0) {|s,t| # apply each class comparison and require nonzero matches if s if t[0].ancestors.include?(t[1]) s += t[1].ancestors.size else s = nil end end } || 0 else score = 0 end score += (1 + nreq) if args.size == params.size [ score, candidate ] }.max {|a,b| a[0] <=> b[0]} (not score or score == 0) ? obj_method_missing.call : method(best)[*args, &block] end