functional-ruby 0.7.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -152
  3. data/doc/memo.txt +192 -0
  4. data/doc/pattern_matching.txt +485 -0
  5. data/doc/protocol.txt +221 -0
  6. data/doc/record.txt +144 -0
  7. data/doc/thread_safety.txt +8 -0
  8. data/lib/functional.rb +48 -18
  9. data/lib/functional/abstract_struct.rb +161 -0
  10. data/lib/functional/delay.rb +117 -0
  11. data/lib/functional/either.rb +222 -0
  12. data/lib/functional/memo.rb +93 -0
  13. data/lib/functional/method_signature.rb +72 -0
  14. data/lib/functional/option.rb +209 -0
  15. data/lib/functional/pattern_matching.rb +117 -100
  16. data/lib/functional/protocol.rb +157 -0
  17. data/lib/functional/protocol_info.rb +193 -0
  18. data/lib/functional/record.rb +155 -0
  19. data/lib/functional/type_check.rb +112 -0
  20. data/lib/functional/union.rb +152 -0
  21. data/lib/functional/version.rb +3 -1
  22. data/spec/functional/abstract_struct_shared.rb +154 -0
  23. data/spec/functional/complex_pattern_matching_spec.rb +205 -0
  24. data/spec/functional/configuration_spec.rb +17 -0
  25. data/spec/functional/delay_spec.rb +147 -0
  26. data/spec/functional/either_spec.rb +237 -0
  27. data/spec/functional/memo_spec.rb +207 -0
  28. data/spec/functional/option_spec.rb +292 -0
  29. data/spec/functional/pattern_matching_spec.rb +279 -276
  30. data/spec/functional/protocol_info_spec.rb +444 -0
  31. data/spec/functional/protocol_spec.rb +274 -0
  32. data/spec/functional/record_spec.rb +175 -0
  33. data/spec/functional/type_check_spec.rb +103 -0
  34. data/spec/functional/union_spec.rb +110 -0
  35. data/spec/spec_helper.rb +6 -4
  36. metadata +55 -45
  37. data/lib/functional/behavior.rb +0 -138
  38. data/lib/functional/behaviour.rb +0 -2
  39. data/lib/functional/catalog.rb +0 -487
  40. data/lib/functional/collection.rb +0 -403
  41. data/lib/functional/inflect.rb +0 -127
  42. data/lib/functional/platform.rb +0 -120
  43. data/lib/functional/search.rb +0 -132
  44. data/lib/functional/sort.rb +0 -41
  45. data/lib/functional/utilities.rb +0 -189
  46. data/md/behavior.md +0 -188
  47. data/md/catalog.md +0 -32
  48. data/md/collection.md +0 -32
  49. data/md/inflect.md +0 -32
  50. data/md/pattern_matching.md +0 -512
  51. data/md/platform.md +0 -32
  52. data/md/search.md +0 -32
  53. data/md/sort.md +0 -32
  54. data/md/utilities.md +0 -55
  55. data/spec/functional/behavior_spec.rb +0 -528
  56. data/spec/functional/catalog_spec.rb +0 -1206
  57. data/spec/functional/collection_spec.rb +0 -752
  58. data/spec/functional/inflect_spec.rb +0 -85
  59. data/spec/functional/integration_spec.rb +0 -205
  60. data/spec/functional/platform_spec.rb +0 -501
  61. data/spec/functional/search_spec.rb +0 -187
  62. data/spec/functional/sort_spec.rb +0 -61
  63. data/spec/functional/utilities_spec.rb +0 -277
@@ -0,0 +1,93 @@
1
+ require 'thread'
2
+
3
+ module Functional
4
+
5
+ # Memoization is a technique for optimizing functions that are time-consuming
6
+ # and/or involve expensive calculations. Every time a memoized function is
7
+ # called the result is caches with reference to the given parameters.
8
+ # Subsequent calls to the function that use the same parameters will return
9
+ # the cached result. As a result the response time for frequently called
10
+ # functions is vastly incresed (after the first call with any given set of)
11
+ # arguments, at the cost of increased memory usage (the cache).
12
+ #
13
+ # @!macro memoize
14
+ #
15
+ # @note Memoized method calls are thread safe and can safely be used in concurrent systems.
16
+ # Declaring memoization on a function is *not* thread safe and should only be done during
17
+ # application initialization.
18
+ module Memo
19
+
20
+ def self.extended(base)
21
+ base.extend(ClassMethods)
22
+ base.send(:__method_memos__=, {})
23
+ super(base)
24
+ end
25
+
26
+ def self.included(base)
27
+ base.extend(ClassMethods)
28
+ base.send(:__method_memos__=, {})
29
+ super(base)
30
+ end
31
+
32
+ # @!visibility private
33
+ module ClassMethods
34
+
35
+ # @!visibility private
36
+ Memo = Struct.new(:function, :mutex, :cache, :max_cache) do
37
+ def max_cache?
38
+ max_cache > 0 && cache.size >= max_cache
39
+ end
40
+ end
41
+
42
+ # @!visibility private
43
+ attr_accessor :__method_memos__
44
+
45
+ # Returns a memoized version of a referentially transparent function. The
46
+ # memoized version of the function keeps a cache of the mapping from arguments
47
+ # to results and, when calls with the same arguments are repeated often, has
48
+ # higher performance at the expense of higher memory use.
49
+ #
50
+ # @param [Symbol] func the class/module function to memoize
51
+ # @param [Hash] opts the options controlling memoization
52
+ # @option opts [Fixnum] :at_most the maximum number of memos to store in the
53
+ # cache; a value of zero (the default) or `nil` indicates no limit
54
+ #
55
+ # @raise [ArgumentError] when the method has already been memoized
56
+ # @raise [ArgumentError] when :at_most option is a negative number
57
+ def memoize(func, opts = {})
58
+ func = func.to_sym
59
+ max_cache = opts[:at_most].to_i
60
+ raise ArgumentError.new("method :#{func} has already been memoized") if __method_memos__.has_key?(func)
61
+ raise ArgumentError.new(':max_cache must be > 0') if max_cache < 0
62
+ __method_memos__[func] = Memo.new(method(func), Mutex.new, {}, max_cache.to_i)
63
+ __define_memo_proxy__(func)
64
+ nil
65
+ end
66
+
67
+ # @!visibility private
68
+ def __define_memo_proxy__(func)
69
+ self.class_eval <<-RUBY
70
+ def self.#{func}(*args, &block)
71
+ self.__proxy_memoized_method__(:#{func}, *args, &block)
72
+ end
73
+ RUBY
74
+ end
75
+
76
+ # @!visibility private
77
+ def __proxy_memoized_method__(func, *args, &block)
78
+ memo = self.__method_memos__[func]
79
+ memo.mutex.lock
80
+ if block_given?
81
+ memo.function.call(*args, &block)
82
+ elsif memo.cache.has_key?(args)
83
+ memo.cache[args]
84
+ else
85
+ result = memo.function.call(*args)
86
+ memo.cache[args] = result unless memo.max_cache?
87
+ end
88
+ ensure
89
+ memo.mutex.unlock
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,72 @@
1
+ module Functional
2
+
3
+ module PatternMatching
4
+
5
+ # @!visibility private
6
+ #
7
+ # Helper functions used when pattern matching runtime arguments against
8
+ # a method defined with the `defn` function of Functional::PatternMatching.
9
+ module MethodSignature
10
+ extend self
11
+
12
+ # Do the given arguments match the given function pattern?
13
+ #
14
+ # @return [Boolean] true when there is a match else false
15
+ def match?(pattern, args)
16
+ return false unless valid_pattern?(args, pattern)
17
+
18
+ pattern.length.times.all? do |index|
19
+ param = pattern[index]
20
+ arg = args[index]
21
+
22
+ all_param_and_last_arg?(pattern, param, index) ||
23
+ arg_is_type_of_param?(param, arg) ||
24
+ hash_param_with_matching_arg?(param, arg) ||
25
+ param_matches_arg?(param, arg)
26
+ end
27
+ end
28
+
29
+ # Is the given pattern a valid pattern with respect to the given
30
+ # runtime arguments?
31
+ #
32
+ # @return [Boolean] true when the pattern is valid else false
33
+ def valid_pattern?(args, pattern)
34
+ (pattern.last == PatternMatching::ALL && args.length >= pattern.length) \
35
+ || (args.length == pattern.length)
36
+ end
37
+
38
+ # Is this the last parameter and is it `ALL`?
39
+ #
40
+ # @return [Boolean] true when matching else false
41
+ def all_param_and_last_arg?(pattern, param, index)
42
+ param == PatternMatching::ALL && index+1 == pattern.length
43
+ end
44
+
45
+ # Is the parameter a class and is the provided argument an instance
46
+ # of that class?
47
+ #
48
+ # @return [Boolean] true when matching else false
49
+ def arg_is_type_of_param?(param, arg)
50
+ param.is_a?(Class) && arg.is_a?(param)
51
+ end
52
+
53
+ # Is the given parameter a Hash and does it match the given
54
+ # runtime argument?
55
+ #
56
+ # @return [Boolean] true when matching else false
57
+ def hash_param_with_matching_arg?(param, arg)
58
+ param.is_a?(Hash) && arg.is_a?(Hash) && ! param.empty? && param.all? do |key, value|
59
+ arg.has_key?(key) && (value == PatternMatching::UNBOUND || arg[key] == value)
60
+ end
61
+ end
62
+
63
+ # Does the given parameter exactly match the given runtime
64
+ # argument or is the parameter `UNBOUND`?
65
+ #
66
+ # @return [Boolean] true when matching else false
67
+ def param_matches_arg?(param, arg)
68
+ param == PatternMatching::UNBOUND || param == arg
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,209 @@
1
+ require_relative 'abstract_struct'
2
+ require_relative 'either'
3
+ require_relative 'protocol'
4
+
5
+ Functional::SpecifyProtocol(:Option) do
6
+ instance_method :some?, 0
7
+ instance_method :none?, 0
8
+ instance_method :some, 0
9
+ end
10
+
11
+ module Functional
12
+
13
+ # An optional value that may be none (no value) or some (a value).
14
+ # This type is a replacement for the use of nil with better type checks.
15
+ # It is an immutable data structure that extends `AbstractStruct`.
16
+ #
17
+ # @see Functional::AbstractStruct
18
+ # @see http://functionaljava.googlecode.com/svn/artifacts/3.0/javadoc/index.html Functional Java
19
+ #
20
+ # @!macro thread_safe_immutable_object
21
+ class Option
22
+ include AbstractStruct
23
+
24
+ # @!visibility private
25
+ NO_OPTION = Object.new.freeze
26
+
27
+ self.datatype = :option
28
+ self.fields = [:some].freeze
29
+
30
+ private_class_method :new
31
+
32
+ # The reason for the absence of a value when none,
33
+ # defaults to nil
34
+ attr_reader :reason
35
+
36
+ class << self
37
+
38
+ # Construct an `Option` with no value.
39
+ #
40
+ # @return [Option] the new option
41
+ def none(reason = nil)
42
+ new(nil, true, reason).freeze
43
+ end
44
+
45
+ # Construct an `Option` with the given value.
46
+ #
47
+ # @param [Object] value the value of the option
48
+ # @return [Option] the new option
49
+ def some(value)
50
+ new(value, false).freeze
51
+ end
52
+ end
53
+
54
+ # Does the option have a value?
55
+ #
56
+ # @return [Boolean] true if some else false
57
+ def some?
58
+ ! none?
59
+ end
60
+ alias_method :value?, :some?
61
+ alias_method :fulfilled?, :some?
62
+
63
+ # Is the option absent a value?
64
+ #
65
+ # @return [Boolean] true if none else false
66
+ def none?
67
+ @none
68
+ end
69
+ alias_method :reason?, :none?
70
+ alias_method :rejected?, :none?
71
+
72
+ # The value of this option.
73
+ #
74
+ # @return [Object] the value when some else nil
75
+ def some
76
+ to_h[:some]
77
+ end
78
+ alias_method :value, :some
79
+
80
+ # Returns the length of this optional value;
81
+ # 1 if there is a value, 0 otherwise.
82
+ #
83
+ # @return [Fixnum] The length of this optional value;
84
+ # 1 if there is a value, 0 otherwise.
85
+ def length
86
+ none? ? 0 : 1
87
+ end
88
+ alias_method :size, :length
89
+
90
+ # Perform a logical `and` operation against this option and the
91
+ # provided option or block. Returns true if this option is some and:
92
+ #
93
+ # * other is an `Option` with some value
94
+ # * other is a truthy value (not nil or false)
95
+ # * the result of the block is a truthy value
96
+ #
97
+ # If a block is given the value of the current option is passed to the
98
+ # block and the result of block processing will be evaluated for its
99
+ # truthiness. An exception will be raised if an other value and a
100
+ # block are both provided.
101
+ #
102
+ # @param [Object] other the value to be evaluated against this option
103
+ # @yieldparam [Object] value the value of this option when some
104
+ # @return [Boolean] true when the union succeeds else false
105
+ # @raise [ArgumentError] when given both other and a block
106
+ def and(other = NO_OPTION)
107
+ raise ArgumentError.new('cannot give both an option and a block') if other != NO_OPTION && block_given?
108
+ return false if none?
109
+
110
+ if block_given?
111
+ !! yield(some)
112
+ elsif Protocol::Satisfy? other, :Option
113
+ other.some?
114
+ else
115
+ !! other
116
+ end
117
+ end
118
+
119
+ # Perform a logical `or` operation against this option and the
120
+ # provided option or block. Returns true if this option is some.
121
+ # If this option is none it returns true if:
122
+ #
123
+ # * other is an `Option` with some value
124
+ # * other is a truthy value (not nil or false)
125
+ # * the result of the block is a truthy value
126
+ #
127
+ # If a block is given the value of the result of block processing
128
+ # will be evaluated for its truthiness. An exception will be raised
129
+ # if an other value and a block are both provided.
130
+ #
131
+ # @param [Object] other the value to be evaluated against this option
132
+ # @return [Boolean] true when the intersection succeeds else false
133
+ # @raise [ArgumentError] when given both other and a block
134
+ def or(other = NO_OPTION)
135
+ raise ArgumentError.new('cannot give both an option and a block') if other != NO_OPTION && block_given?
136
+ return true if some?
137
+
138
+ if block_given?
139
+ !! yield
140
+ elsif Protocol::Satisfy? other, :Option
141
+ other.some?
142
+ else
143
+ !! other
144
+ end
145
+ end
146
+
147
+ # Returns the value of this option when some else returns the
148
+ # value of the other option or block. When the other is also an
149
+ # option its some value is returned. When the other is any other
150
+ # value it is simply passed through. When a block is provided the
151
+ # block is processed and the return value of the block is returned.
152
+ # An exception will be raised if an other value and a block are
153
+ # both provided.
154
+ #
155
+ # @param [Object] other the value to be evaluated when this is none
156
+ # @return [Object] this value when some else the value of other
157
+ # @raise [ArgumentError] when given both other and a block
158
+ def else(other = NO_OPTION)
159
+ raise ArgumentError.new('cannot give both an option and a block') if other != NO_OPTION && block_given?
160
+ return some if some?
161
+
162
+ if block_given?
163
+ yield
164
+ elsif Protocol::Satisfy? other, :Option
165
+ other.some
166
+ else
167
+ other
168
+ end
169
+ end
170
+
171
+ # If the condition satisfies, return the given A in some, otherwise, none.
172
+ #
173
+ # @param [Object] value The some value to use if the condition satisfies.
174
+ # @param [Boolean] condition The condition to test (when no block given).
175
+ # @yield The condition to test (when no condition given).
176
+ #
177
+ # @return [Option] A constructed option based on the given condition.
178
+ #
179
+ # @raise [ArgumentError] When both a condition and a block are given.
180
+ def self.iff(value, condition = NO_OPTION)
181
+ raise ArgumentError.new('requires either a condition or a block, not both') if condition != NO_OPTION && block_given?
182
+ condition = block_given? ? yield : !! condition
183
+ condition ? some(value) : none
184
+ end
185
+
186
+ # @!macro inspect_method
187
+ def inspect
188
+ super.gsub(/ :some/, " (#{some? ? 'some' : 'none'}) :some")
189
+ end
190
+ alias_method :to_s, :inspect
191
+
192
+ private
193
+
194
+ # Create a new Option with the given value and disposition.
195
+ #
196
+ # @param [Object] value the value of this option
197
+ # @param [Boolean] none is this option absent a value?
198
+ # @param [Object] reason the reason for the absense of a value
199
+ #
200
+ # @!visibility private
201
+ def initialize(value, none, reason = nil)
202
+ @none = none
203
+ @reason = none ? reason : nil
204
+ hsh = none ? {some: nil} : {some: value}
205
+ set_data_hash(hsh)
206
+ set_values_array(hsh.values)
207
+ end
208
+ end
209
+ end
@@ -1,132 +1,149 @@
1
- module PatternMatching
2
-
3
- UNBOUND = Class.new
4
- ALL = Class.new
5
-
6
- private
7
-
8
- GUARD_CLAUSE = Class.new do # :nodoc:
9
- def initialize(func, clazz, matcher) # :nodoc:
10
- @func = func
11
- @clazz = clazz
12
- @matcher = matcher
13
- end
14
- def when(&block) # :nodoc:
15
- unless block_given?
16
- raise ArgumentError.new("block missing for `when` guard on function `#{@func}` of class #{@clazz}")
1
+ require_relative 'method_signature'
2
+
3
+ module Functional
4
+
5
+ # As much as I love Ruby I've always been a little disappointed that Ruby doesn't
6
+ # support function overloading. Function overloading tends to reduce branching
7
+ # and keep function signatures simpler. No sweat, I learned to do without. Then
8
+ # I started programming in Erlang. My favorite Erlang feature is, without
9
+ # question, pattern matching. Pattern matching is like function overloading
10
+ # cranked to 11. So one day I was musing on Twitter that I'd like to see
11
+ # Erlang-stype pattern matching in Ruby and one of my friends responded
12
+ # "Build it!" So I did. And here it is.
13
+ #
14
+ # @!macro pattern_matching
15
+ module PatternMatching
16
+
17
+ # A parameter that is required but that can take any value.
18
+ # @!visibility private
19
+ UNBOUND = Object.new.freeze
20
+
21
+ # A match for one or more parameters in the last position of the match.
22
+ # @!visibility private
23
+ ALL = Object.new.freeze
24
+
25
+ private
26
+
27
+ # A guard clause on a pattern match.
28
+ # @!visibility private
29
+ GuardClause = Class.new do
30
+ def initialize(function, clazz, pattern)
31
+ @function = function
32
+ @clazz = clazz
33
+ @pattern = pattern
17
34
  end
18
- @matcher[@matcher.length-1] = block
19
- return nil
20
- end
21
- end
22
-
23
- def self.__match_pattern__(args, pattern) # :nodoc:
24
- return unless (pattern.last == ALL && args.length >= pattern.length) \
25
- || (args.length == pattern.length)
26
- pattern.each_with_index do |p, i|
27
- break if p == ALL && i+1 == pattern.length
28
- arg = args[i]
29
- next if p.is_a?(Class) && arg.is_a?(p)
30
- if p.is_a?(Hash) && arg.is_a?(Hash) && ! p.empty?
31
- p.each do |key, value|
32
- return false unless arg.has_key?(key)
33
- next if value == UNBOUND
34
- return false unless arg[key] == value
35
+ def when(&block)
36
+ unless block_given?
37
+ raise ArgumentError.new("block missing for `when` guard on function `#{@function}` of class #{@clazz}")
35
38
  end
36
- next
39
+ @pattern.guard = block
40
+ self
37
41
  end
38
- return false unless p == UNBOUND || p == arg
39
42
  end
40
- return true
41
- end
42
43
 
43
- def self.__unbound_args__(match, args) # :nodoc:
44
- argv = []
45
- match.first.each_with_index do |p, i|
46
- if p == ALL && i == match.first.length-1
47
- argv << args[(i..args.length)].reduce([]){|memo, arg| memo << arg }
48
- elsif p.is_a?(Hash) && p.values.include?(UNBOUND)
49
- p.each do |key, value|
50
- argv << args[i][key] if value == UNBOUND
44
+ # @!visibility private
45
+ FunctionPattern = Struct.new(:function, :args, :body, :guard)
46
+
47
+ # @!visibility private
48
+ def __unbound_args__(match, args)
49
+ argv = []
50
+ match.args.each_with_index do |p, i|
51
+ if p == ALL && i == match.args.length-1
52
+ argv << args[(i..args.length)].reduce([]){|memo, arg| memo << arg }
53
+ elsif p.is_a?(Hash) && p.values.include?(UNBOUND)
54
+ p.each do |key, value|
55
+ argv << args[i][key] if value == UNBOUND
56
+ end
57
+ elsif p.is_a?(Hash) || p == UNBOUND || p.is_a?(Class)
58
+ argv << args[i]
51
59
  end
52
- elsif p.is_a?(Hash) || p == UNBOUND || p.is_a?(Class)
53
- argv << args[i]
54
60
  end
61
+ argv
55
62
  end
56
- return argv
57
- end
58
-
59
- def self.__pattern_match__(clazz, func, *args, &block) # :nodoc:
60
- args = args.first
61
63
 
62
- matchers = clazz.__function_pattern_matches__[func]
63
- return [:nodef, nil] if matchers.nil?
64
+ def __pass_guard__?(matcher, args)
65
+ matcher.guard.nil? ||
66
+ self.instance_exec(*__unbound_args__(matcher, args), &matcher.guard)
67
+ end
64
68
 
65
- match = matchers.detect do |matcher|
66
- if PatternMatching.__match_pattern__(args, matcher.first)
67
- if matcher.last.nil?
68
- true # no guard clause
69
- else
70
- self.instance_exec(*PatternMatching.__unbound_args__(matcher, args), &matcher.last)
71
- end
69
+ # @!visibility private
70
+ def __pattern_match__(clazz, function, *args, &block)
71
+ args = args.first
72
+ matchers = clazz.__function_pattern_matches__.fetch(function, [])
73
+ matchers.detect do |matcher|
74
+ MethodSignature.match?(matcher.args, args) && __pass_guard__?(matcher, args)
72
75
  end
73
76
  end
74
77
 
75
- return (match ? [:ok, match] : [:nomatch, nil])
76
- end
77
-
78
- protected
79
-
80
- def self.included(base)
81
-
82
- class << base
78
+ def self.included(base)
79
+ base.extend(ClassMethods)
80
+ super(base)
81
+ end
83
82
 
84
- public
83
+ # Class methods added to a class that includes {Functional::PatternMatching}
84
+ # @!visibility private
85
+ module ClassMethods
85
86
 
86
- def _() # :nodoc:
87
- return UNBOUND
87
+ # @!visibility private
88
+ def _()
89
+ UNBOUND
88
90
  end
89
91
 
90
- def defn(func, *args, &block)
92
+ # @!visibility private
93
+ def defn(function, *args, &block)
91
94
  unless block_given?
92
- raise ArgumentError.new("block missing for definition of function `#{func}` on class #{self}")
95
+ raise ArgumentError.new("block missing for definition of function `#{function}` on class #{self}")
93
96
  end
94
97
 
95
- pattern = __add_pattern_for__(func, *args, &block)
96
-
97
- unless self.instance_methods(false).include?(func)
98
-
99
- define_method(func) do |*args, &block|
100
- result, match = PatternMatching.__pattern_match__(self.method(func).owner, func, args, block)
101
- if result == :ok
102
- # if a match is found call the block
103
- argv = PatternMatching.__unbound_args__(match, args)
104
- return self.instance_exec(*argv, &match[1])
105
- else # if result == :nodef || result == :nomatch
106
- begin
107
- super(*args, &block)
108
- rescue NoMethodError, ArgumentError
109
- raise NoMethodError.new("no method `#{func}` matching #{args} found for class #{self.class}")
110
- end
111
- end
112
- end
98
+ # add a new pattern for this function
99
+ pattern = __register_pattern__(function, *args, &block)
100
+
101
+ # define the delegator function if it doesn't exist yet
102
+ unless self.instance_methods(false).include?(function)
103
+ __define_method_with_matching__(function)
113
104
  end
114
105
 
115
- return GUARD_CLAUSE.new(func, self, pattern)
106
+ # return a guard clause to be added to the pattern
107
+ GuardClause.new(function, self, pattern)
116
108
  end
117
109
 
118
- public
110
+ # @!visibility private
111
+ # define an arity -1 function that dispatches to the appropriate
112
+ # pattern match variant or raises an exception
113
+ def __define_method_with_matching__(function)
114
+ define_method(function) do |*args, &block|
115
+ begin
116
+ # get the collection of matched patterns for this function
117
+ # use owner to ensure we climb the inheritance tree
118
+ match = __pattern_match__(self.method(function).owner, function, args, block)
119
+ if match
120
+ # call the matched function
121
+ argv = __unbound_args__(match, args)
122
+ self.instance_exec(*argv, &match.body)
123
+ else
124
+ # delegate to the superclass
125
+ super(*args, &block)
126
+ end
127
+ rescue NoMethodError, ArgumentError
128
+ # raise a custom error
129
+ raise NoMethodError.new("no method `#{function}` matching #{args} found for class #{self.class}")
130
+ end
131
+ end
132
+ end
119
133
 
120
- def __function_pattern_matches__ # :nodoc:
134
+ # @!visibility private
135
+ def __function_pattern_matches__
121
136
  @__function_pattern_matches__ ||= Hash.new
122
137
  end
123
138
 
124
- def __add_pattern_for__(func, *args, &block) # :nodoc:
139
+ # @!visibility private
140
+ def __register_pattern__(function, *args, &block)
125
141
  block = Proc.new{} unless block_given?
126
- matchers = self.__function_pattern_matches__
127
- matchers[func] = [] unless matchers.has_key?(func)
128
- matchers[func] << [args, block, nil]
129
- return matchers[func].last
142
+ pattern = FunctionPattern.new(function, args, block)
143
+ patterns = self.__function_pattern_matches__.fetch(function, [])
144
+ patterns << pattern
145
+ self.__function_pattern_matches__[function] = patterns
146
+ pattern
130
147
  end
131
148
  end
132
149
  end