Class: Multimethod::Multimethod
- Inherits:
-
Object
- Object
- Multimethod::Multimethod
- Defined in:
- lib/multimethod/multimethod.rb
Overview
Represents a Multimethod.
A Multimethod has multiple implementations of a method based on the relative scoring of the argument types of the message.
A Multimethod has a name.
Constant Summary collapse
- @@name_map =
Support
{ '@' => 'AT', '=' => 'EQ', '<' => 'LT', '>' => 'GT', '+' => 'ADD', '-' => 'SUB', '*' => 'MUL', '/' => 'DIV', '%' => 'MOD', '^' => 'XOR', '|' => 'OR', '&' => 'AND', '!' => 'NOT', '~' => 'TIL', '_' => '', # escape '_' as '_' nil => nil }
- @@name_rx =
Regexp.new('(' + @@name_map.keys.collect{|x| Regexp.quote(x)}.join('|') + ')')
Instance Attribute Summary collapse
-
#debug ⇒ Object
Enable debugging info.
-
#method ⇒ Object
A list of Method’s that implement this Multimethod.
-
#name ⇒ Object
The Multimethod’s name.
-
#table ⇒ Object
The Multimethod::Table that owns this Multimethod.
Class Method Summary collapse
Instance Method Summary collapse
-
#add_method(method) ⇒ Object
Adds the new Method object to this Multimethod.
- #ambiguous_methods(scores) ⇒ Object
-
#apply_method(meth, rcvr, args) ⇒ Object
Interface to Multimethod::Table.
- #dispatch(rcvr, args) ⇒ Object
-
#find_method(signature) ⇒ Object
Returns a list of all Methods that match the Signature.
-
#gensym(name = nil) ⇒ Object
Generates a unique symbol for a method name.
-
#initialize(name, *opts) ⇒ Multimethod
constructor
Initialize a new Multimethod.
-
#install_dispatch(mod) ⇒ Object
Installs a dispatching method in the Module.
- #lookup_method(rcvr, args) ⇒ Object
- #lookup_method_(args) ⇒ Object
- #lookup_method_cached_(args) ⇒ Object
-
#matches_signature(signature) ⇒ Object
Returns true if this Multimethod matches the Signature.
-
#new_method(mod, name, *args) ⇒ Object
Creates a new Method object bound to mod by name.
-
#new_method_from_signature(signature) ⇒ Object
Create a new Method object using the Signature.
-
#remove_dispatch(mod) ⇒ Object
Removes the dispatching method in the Module.
-
#remove_method(x) ⇒ Object
Removes the method.
-
#score_methods(meths, args) ⇒ Object
Returns a sorted list of scores and Methods that match the argument types.
Constructor Details
#initialize(name, *opts) ⇒ Multimethod
Initialize a new Multimethod.
24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/multimethod/multimethod.rb', line 24 def initialize(name, *opts) raise NameError, "multimethod name not specified" unless name && name.to_s.size > 0 @debug = nil @name = name @name_i = 0 @method = [ ] @dispatch = { } @lookup_method = { } end |
Instance Attribute Details
#debug ⇒ Object
Enable debugging info.
21 22 23 |
# File 'lib/multimethod/multimethod.rb', line 21 def debug @debug end |
#method ⇒ Object
A list of Method’s that implement this Multimethod.
15 16 17 |
# File 'lib/multimethod/multimethod.rb', line 15 def method @method end |
#name ⇒ Object
The Multimethod’s name.
12 13 14 |
# File 'lib/multimethod/multimethod.rb', line 12 def name @name end |
#table ⇒ Object
The Multimethod::Table that owns this Multimethod.
18 19 20 |
# File 'lib/multimethod/multimethod.rb', line 18 def table @table end |
Class Method Details
.normalize_name(name) ⇒ Object
261 262 263 264 265 266 |
# File 'lib/multimethod/multimethod.rb', line 261 def self.normalize_name(name) name = name.to_s.clone name.gsub!(@@name_rx){|x| "_#{@@name_map[x] || '_'}_"} name.intern end |
Instance Method Details
#add_method(method) ⇒ Object
Adds the new Method object to this Multimethod.
62 63 64 65 66 67 68 69 |
# File 'lib/multimethod/multimethod.rb', line 62 def add_method(method) # THREAD CRITICAL BEGIN remove_method(method.signature) @method.push(method) method.multimethod = self @lookup_method = { } # flush cache # THREAD CRITICAL END end |
#ambiguous_methods(scores) ⇒ Object
172 173 174 175 176 177 178 179 |
# File 'lib/multimethod/multimethod.rb', line 172 def ambiguous_methods(scores) # Check for additional methods with the same score as # the first method. scores = scores.select{|x| x[0] == scores[0][0]} # Map to a list of signatures. scores.size > 1 ? scores.collect{|x| x[1].signature} : nil end |
#apply_method(meth, rcvr, args) ⇒ Object
Interface to Multimethod::Table
115 116 117 118 119 120 121 122 123 |
# File 'lib/multimethod/multimethod.rb', line 115 def apply_method(meth, rcvr, args) unless meth # && false $stderr.puts "Available multimethods for #{rcvr.class.name}##{@name}(#{args}):" $stderr.puts " " + @method.sort{|a,b| a.min_args <=> b.min_args }.collect{|x| x.to_s(name)}.join("\n ") $stderr.puts "\n" end raise NameError, "Cannot find multimethod for #{rcvr.class.name}##{@name}(#{args})" unless meth rcvr.send(meth.impl_name, *args) end |
#dispatch(rcvr, args) ⇒ Object
109 110 111 |
# File 'lib/multimethod/multimethod.rb', line 109 def dispatch(rcvr, args) apply_method(lookup_method(rcvr, args), rcvr, args) end |
#find_method(signature) ⇒ Object
Returns a list of all Methods that match the Signature.
79 80 81 82 83 |
# File 'lib/multimethod/multimethod.rb', line 79 def find_method(signature) m = @method.select{|x| x.matches_signature(signature)} m end |
#gensym(name = nil) ⇒ Object
Generates a unique symbol for a method name. Method implementations will use a unique name for the implementation method. For example, for a Multimethod named “foo”, the Method name might be “_multimethod_12_foo”.
41 42 43 44 |
# File 'lib/multimethod/multimethod.rb', line 41 def gensym(name = nil) name ||= @name "_multimethod_#{@name_i = @name_i + 1}_#{name}" end |
#install_dispatch(mod) ⇒ Object
Installs a dispatching method in the Module. This method will dispatch to the Multimethod for Method lookup and application.
207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/multimethod/multimethod.rb', line 207 def install_dispatch(mod) # THREAD CRITICAL BEGIN unless @dispatch[mod] @dispatch[mod] = true # $stderr.puts "install_dispatch(#{name}) into #{mod}\n"; mod.class_eval(body = <<-"end_eval", __FILE__, __LINE__) def #{name}(*args) ::#{table.class.name}.instance.dispatch(#{name.inspect}, self, args) end end_eval # $stderr.puts "install_dispatch = #{body}" end # THREAD CRITICAL END end |
#lookup_method(rcvr, args) ⇒ Object
126 127 128 129 130 |
# File 'lib/multimethod/multimethod.rb', line 126 def lookup_method(rcvr, args) args = args.clone args.unshift(rcvr) lookup_method_cached_(args) end |
#lookup_method_(args) ⇒ Object
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/multimethod/multimethod.rb', line 147 def lookup_method_(args) scores = score_methods(@method, args) if scores.empty? # Method not found. result = nil else # Check for ambiguous methods. if result = ambiguous_methods(scores) raise NameError, "Ambiguous methods for multimethod '#{@name}' for (#{args.collect{|x| x.name}.join(', ')}): #{result.collect{|x| x.to_s}.join(', ')}" end # Select best scoring method. result = scores[0][1] end #if @name.to_s == 'bar' # $stderr.puts "args = " + args.collect{|x| x.class.name + ' ' + x.to_s}.join(", ") # $stderr.puts "scores:\n " + scores.collect{|x| x.inspect}.join("\n ") # end result end |
#lookup_method_cached_(args) ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/multimethod/multimethod.rb', line 133 def lookup_method_cached_(args) args_type = args.collect{|x| x.class} # THREAD CRITICAL BEGIN unless result = @lookup_method[args_type] result = @lookup_method[args_type] = lookup_method_(args_type) end # THREAD CRITICAL END result end |
#matches_signature(signature) ⇒ Object
Returns true if this Multimethod matches the Signature.
73 74 75 |
# File 'lib/multimethod/multimethod.rb', line 73 def matches_signature(signature) @name == signature.name end |
#new_method(mod, name, *args) ⇒ Object
Creates a new Method object bound to mod by name.
47 48 49 50 51 |
# File 'lib/multimethod/multimethod.rb', line 47 def new_method(mod, name, *args) m = Method.new(gensym(name), mod, name, *args) add_method(m) m end |
#new_method_from_signature(signature) ⇒ Object
Create a new Method object using the Signature.
54 55 56 57 58 |
# File 'lib/multimethod/multimethod.rb', line 54 def new_method_from_signature(signature) m = Method.new(gensym(name), signature) add_method(m) m end |
#remove_dispatch(mod) ⇒ Object
Removes the dispatching method in the Module.
224 225 226 227 228 229 230 231 232 |
# File 'lib/multimethod/multimethod.rb', line 224 def remove_dispatch(mod) # THREAD CRITICAL BEGIN if @dispatch[mod] @dispatch[mod] = false # $stderr.puts "Removing dispatch for #{mod.name}##{name}" mod.class_eval("remove_method #{name.inspect}") end # THREAD CRITICAL END end |
#remove_method(x) ⇒ Object
Removes the method.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/multimethod/multimethod.rb', line 86 def remove_method(x) case x when Signature m = find_method(x) m = m[0] return unless m raise("No method found for #{x.to_s}") unless m else m = x end m.remove_implementation m.multimethod = nil @method.delete(m) @lookup_method = { } # flush cache # Remove multimethod dispatch in the method's module? if @method.collect{|x| m.signature.mod = m.signature.mod}.empty? remove_dispatch(m.signature.mod) end end |
#score_methods(meths, args) ⇒ Object
Returns a sorted list of scores and Methods that match the argument types.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/multimethod/multimethod.rb', line 184 def score_methods(meths, args) scores = meths.collect do |meth| score = meth.score_cached(args) if score score = [ score, meth ] else score = nil end score end scores.compact! scores.sort! $stderr.puts %{ score_methods(#{args.inspect}) => \n#{scores.collect{|x| x.inspect}.join("\n")}} if @debug scores end |