functional-ruby 0.7.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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