pattern-matching 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZDIzMzQ1NTgyM2ZlNTVkMmEzYmI1YTNhZWMzODJjYWY5ODkxNzI5NQ==
4
+ NTczMjQ3ODhmZmM1ZTFjYWI0NDc3Mjk4ZDg0ZWNiZTI2MTEyMWUzNg==
5
5
  data.tar.gz: !binary |-
6
- MTcyYWU2ZjE1MGYxMWQxYWRkMWQwZGEwMDZmNDNiZWRkM2JjNjk1YQ==
6
+ YzQwN2I4N2VhNDhlOGJjZjM0MmZjNGI1OTFkZDE1NWNhMjA3ODU5ZQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- ZDdmMjE5NjFhOTI1MDE4MzFkZjRjMDgxZWY2OGMzNzNlODczMjQ4ZjRiZTE2
10
- MjZkZDNhYjFhOTBmODJiMTFlYzQ5ZjkwNzQzOTRjZmM5YjQzZjI3MTg3MTNi
11
- ZGI5MmI4ZWNmMTYyMTJkNDA3NTc3ZjJlNGEzMzM4NWE0Mzk4MTE=
9
+ YTFiNzcwMzg0NmQ2OGI5MmI2MTA1Njg1YzE2NjIzMDJiNjg5NDk4OGZmMDUw
10
+ YzlmMGI3NzJlMWNmNGViYTcwN2MxODY4MzA4OGRhNzZjOTJlOTg1ODUzZTcy
11
+ NzYyZjgzZjIzZmE2ZjFjMjEzZTIxZDE2OGJlNDEzMDE0MDNkOTU=
12
12
  data.tar.gz: !binary |-
13
- MzJkN2M4M2YzYTQyODkzODI5YzlkNjZmMWFjZDAzY2VlNTg0OGVjOWUzYmRh
14
- NzJiNjFkOWI0ODY5NjE4ODcyZTBiYjljNGYyYWQ5ZGE5ZmNjOThmZTY5YTRh
15
- M2M5NThkNmQ3NGNkMDJjYWEwYTNhZWYwZjJkYmNlYzQ5YmIwNzc=
13
+ MjIxM2ViMmYzY2UyNDhhMWViNzI0NTgxODNmODRmZGEyODY4MGY0YWM0Mjcw
14
+ NWVmZjg1NDhjMjZjNDZjNzI2NTRmNjY3YzBlZjQwYjM4MmZkZmE2NzUwZjYx
15
+ OWMwMTRkOGJiMzBkNDA4OTY1NmU2NmY3NmQ1M2Q3YzdmOWE2NDc=
data/README.md CHANGED
@@ -14,9 +14,17 @@ The project is hosted on the following sites:
14
14
 
15
15
  ## Introduction
16
16
 
17
- [Ruby](http://www.ruby-lang.org/en/) is my favorite programming by far. As much as I love Ruby I've always been a little disappointed that Ruby doesn't support function overloading. Function overloading tends to reduce branching and keep functions signatures simpler. No sweat, I learned to do without. Then I started programming in [Erlang](http://www.erlang.org/)…
18
-
19
- I've really started to enjoy working in Erlang. Erlang is good at all the things Ruby is bad at and vice versa. Together, Ruby and Erlang make me happy. My favotite Erlang feature is, without question, [pattern matching](http://learnyousomeerlang.com/syntax-in-functions#pattern-matching). Pattern matching is like function overloading cranked to 11. So one day I was musing on Twitter and one of my friends responded with "Build it!" So I did. And here it is.
17
+ [Ruby](http://www.ruby-lang.org/en/) is my favorite programming by far. As much as I love
18
+ Ruby I've always been a little disappointed that Ruby doesn't support function overloading.
19
+ Function overloading tends to reduce branching and keep function signatures simpler.
20
+ No sweat, I learned to do without. Then I started programming in [Erlang](http://www.erlang.org/)...
21
+
22
+ I've really started to enjoy working in Erlang. Erlang is good at all the things Ruby is bad
23
+ at and vice versa. Together, Ruby and Erlang make me happy. My favorite Erlang feature is,
24
+ without question, [pattern matching](http://learnyousomeerlang.com/syntax-in-functions#pattern-matching).
25
+ Pattern matching is like function overloading cranked to 11. So one day I was musing on Twitter
26
+ that I'd like to see Erlang-stype pattern matching in Ruby and one of my friends responded "Build it!"
27
+ So I did. And here it is.
20
28
 
21
29
  ### Goals
22
30
 
@@ -30,16 +38,21 @@ I've really started to enjoy working in Erlang. Erlang is good at all the things
30
38
 
31
39
  ### Features
32
40
 
33
- * Basic pattern matching for instance methods.
41
+ * Pattern matching for instance methods.
42
+ * Pattern matching for object constructors.
34
43
  * Parameter count matching
35
- * Mathing against primitive values
44
+ * Matching against primitive values
36
45
  * Matching by class/datatype
37
46
  * Matching against specific key/vaue pairs in hashes
38
47
  * Matching against the presence of keys within hashes
39
- * Reasonable error messages when no match is found
40
- * Dispatching to superclass methods when no match is found
48
+ * Implicit hash for last parameter
49
+ * Variable-length parameter lists
50
+ * Guard clauses
41
51
  * Recursive calls to other pattern matches
52
+ * Recursive calls to superclass pattern matches
42
53
  * Recursive calls to superclass methods
54
+ * Dispatching to superclass methods when no match is found
55
+ * Reasonable error messages when no match is found
43
56
 
44
57
  ### To-do
45
58
 
@@ -373,6 +386,19 @@ class Foo
373
386
  end
374
387
  ```
375
388
 
389
+ ### Constructor Overloading
390
+
391
+ ```ruby
392
+ require 'pattern_matching'
393
+
394
+ class Foo
395
+ include PatternMatching
396
+
397
+ defn(:initialize) { @name = 'baz' }
398
+ defn(:initialize, _) {|name| @name = name.to_s }
399
+ end
400
+ ```
401
+
376
402
  ### Matching by Class/Datatype
377
403
 
378
404
  ```ruby
@@ -1,79 +1,93 @@
1
+ require 'pp'
2
+
1
3
  module PatternMatching
2
4
 
3
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
4
6
 
5
7
  UNBOUND = Class.new
6
8
  ALL = Class.new
7
9
 
8
- def self.included(base)
10
+ private
9
11
 
10
- base.instance_variable_set(:@__function_pattern_matches__, Hash.new)
11
-
12
- private
13
-
14
- def __match_pattern__(args, pattern) # :nodoc:
15
- return unless (pattern.last == ALL && args.length >= pattern.length) \
16
- || (args.length == pattern.length)
17
- pattern.each_with_index do |p, i|
18
- break if p == ALL && i+1 == pattern.length
19
- arg = args[i]
20
- next if p.is_a?(Class) && arg.is_a?(p)
21
- if p.is_a?(Hash) && arg.is_a?(Hash) && ! p.empty?
22
- p.each do |key, value|
23
- return false unless arg.has_key?(key)
24
- next if value == UNBOUND
25
- return false unless arg[key] == value
26
- end
27
- next
12
+ class Guard # :nodoc:
13
+ def initialize(func, clazz, matcher) # :nodoc:
14
+ @func = func
15
+ @clazz = clazz
16
+ @matcher = matcher
17
+ end
18
+ def when(&block) # :nodoc:
19
+ unless block_given?
20
+ raise ArgumentError.new("block missing for `when` guard on function `#{@func}` of class #{@clazz}")
21
+ end
22
+ @matcher[@matcher.length-1] = block
23
+ return nil
24
+ end
25
+ end
26
+
27
+ def self.__match_pattern__(args, pattern) # :nodoc:
28
+ return unless (pattern.last == ALL && args.length >= pattern.length) \
29
+ || (args.length == pattern.length)
30
+ pattern.each_with_index do |p, i|
31
+ break if p == ALL && i+1 == pattern.length
32
+ arg = args[i]
33
+ next if p.is_a?(Class) && arg.is_a?(p)
34
+ if p.is_a?(Hash) && arg.is_a?(Hash) && ! p.empty?
35
+ p.each do |key, value|
36
+ return false unless arg.has_key?(key)
37
+ next if value == UNBOUND
38
+ return false unless arg[key] == value
28
39
  end
29
- return false unless p == UNBOUND || p == arg
40
+ next
30
41
  end
31
- return true
42
+ return false unless p == UNBOUND || p == arg
32
43
  end
44
+ return true
45
+ end
33
46
 
34
- def __unbound_args__(match, args) # :nodoc:
35
- argv = []
36
- match.first.each_with_index do |p, i|
37
- if p == ALL && i == match.first.length-1
38
- argv << args[(i..args.length)].reduce([]){|memo, arg| memo << arg }
39
- elsif p.is_a?(Hash) && p.values.include?(UNBOUND)
40
- p.each do |key, value|
41
- argv << args[i][key] if value == UNBOUND
42
- end
43
- elsif p.is_a?(Hash) || p == UNBOUND || p.is_a?(Class)
44
- argv << args[i]
47
+ def self.__unbound_args__(match, args) # :nodoc:
48
+ argv = []
49
+ match.first.each_with_index do |p, i|
50
+ if p == ALL && i == match.first.length-1
51
+ argv << args[(i..args.length)].reduce([]){|memo, arg| memo << arg }
52
+ elsif p.is_a?(Hash) && p.values.include?(UNBOUND)
53
+ p.each do |key, value|
54
+ argv << args[i][key] if value == UNBOUND
45
55
  end
56
+ elsif p.is_a?(Hash) || p == UNBOUND || p.is_a?(Class)
57
+ argv << args[i]
46
58
  end
47
- return argv
48
59
  end
60
+ return argv
61
+ end
49
62
 
50
- def __pattern_match__(func, *args, &block) # :nodoc:
51
- clazz = self.class
52
- args = args.first
63
+ def self.__pattern_match__(clazz, func, *args, &block) # :nodoc:
64
+ args = args.first
53
65
 
54
- # get the array of matchers for this function
55
- matchers = clazz.instance_variable_get(:@__function_pattern_matches__)[func]
66
+ # get the array of matchers for this function
67
+ matchers = clazz.__function_pattern_matches__[func]
68
+ return [:nodef, nil] if matchers.nil?
56
69
 
57
- # scan through all patterns for this function
58
- index = matchers.index do |matcher|
59
- if __match_pattern__(args, matcher.first)
60
- if matcher.last.nil?
61
- true # no guard clause
62
- else
63
- self.instance_exec(*__unbound_args__(matcher, args), &matcher.last)
64
- end
70
+ # scan through all patterns for this function
71
+ index = matchers.index do |matcher|
72
+ if PatternMatching.__match_pattern__(args, matcher.first)
73
+ if matcher.last.nil?
74
+ true # no guard clause
75
+ else
76
+ self.instance_exec(*PatternMatching.__unbound_args__(matcher, args), &matcher.last)
65
77
  end
66
78
  end
79
+ end
67
80
 
68
- if index.nil?
69
- return [:nomatch, nil]
70
- else
71
- # if a match is found call the block
72
- match = matchers[index]
73
- argv = __unbound_args__(match, args)
74
- return [:ok, self.instance_exec(*argv, &match[1])]
75
- end
81
+ if index.nil?
82
+ return [:nomatch, nil]
83
+ else
84
+ return [:ok, matchers[index]]
76
85
  end
86
+ end
87
+
88
+ protected
89
+
90
+ def self.included(base)
77
91
 
78
92
  class << base
79
93
 
@@ -85,49 +99,45 @@ module PatternMatching
85
99
 
86
100
  def defn(func, *args, &block)
87
101
 
88
- guard = Class.new do
89
- def initialize(func, clazz, matcher)
90
- @func = func
91
- @clazz = clazz
92
- @matcher = matcher
93
- end
94
- def when(&block)
95
- unless block_given?
96
- raise ArgumentError.new("block missing for `when` guard on function `#{@func}` of class #{@clazz}")
97
- end
98
- @matcher[@matcher.length-1] = block
99
- return nil
100
- end
101
- end
102
-
103
102
  block = Proc.new{} unless block_given?
104
103
  pattern = __add_pattern_for__(func, *args, &block)
105
104
 
106
105
  unless self.instance_methods(false).include?(func)
107
- self.send(:define_method, func) do |*args, &block|
108
- result, value = __pattern_match__(func, args, block)
109
- return value if result == :ok
110
- begin
106
+
107
+ define_method(func) do |*args, &block|
108
+ result, match = PatternMatching.__pattern_match__(self.method(func).owner, func, args, block)
109
+ if result == :ok
110
+ # if a match is found call the block
111
+ argv = PatternMatching.__unbound_args__(match, args)
112
+ return self.instance_exec(*argv, &match[1])
113
+ elsif result == :nodef
111
114
  super(*args, &block)
112
- rescue NoMethodError
113
- raise NoMethodError.new("no method `#{func}` matching #{args} found for class #{self.class}")
115
+ else
116
+ begin
117
+ super(*args, &block)
118
+ rescue NoMethodError, ArgumentError
119
+ raise NoMethodError.new("no method `#{func}` matching #{args} found for class #{self.class}")
120
+ end
114
121
  end
115
122
  end
116
123
  end
117
124
 
118
- return guard.new(func, self, pattern)
125
+ return PatternMatching::Guard.new(func, self, pattern)
119
126
  end
120
127
 
121
- private
128
+ public
129
+
130
+ def __function_pattern_matches__ # :nodoc:
131
+ @__function_pattern_matches__ ||= Hash.new
132
+ end
122
133
 
123
134
  def __add_pattern_for__(func, *args, &block) # :nodoc:
124
135
  block = Proc.new{} unless block_given?
125
- matchers = self.instance_variable_get(:@__function_pattern_matches__)
136
+ matchers = self.__function_pattern_matches__
126
137
  matchers[func] = [] unless matchers.has_key?(func)
127
138
  matchers[func] << [args, block, nil]
128
139
  return matchers[func].last
129
140
  end
130
-
131
141
  end
132
142
  end
133
143
  end
@@ -1,10 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'ostruct'
3
3
 
4
- describe 'integration' do
5
-
6
4
  class Bar
7
-
8
5
  def greet
9
6
  return 'Hello, World!'
10
7
  end
@@ -15,9 +12,8 @@ describe 'integration' do
15
12
 
16
13
  attr_accessor :name
17
14
 
18
- def initialize(name = 'baz')
19
- @name = name
20
- end
15
+ defn(:initialize) { @name = 'baz' }
16
+ defn(:initialize, _) {|name| @name = name.to_s }
21
17
 
22
18
  defn(:greet, _) do |name|
23
19
  "Hello, #{name}!"
@@ -97,27 +93,46 @@ describe 'integration' do
97
93
  defn(:all, ALL) { | args|
98
94
  args
99
95
  }
100
-
96
+
101
97
  defn(:old_enough, _){ true }.when{|x| x >= 16 }
102
98
  defn(:old_enough, _){ false }
103
-
99
+
104
100
  defn(:right_age, _) {
105
101
  true
106
102
  }.when{|x| x >= 16 && x <= 104 }
107
-
103
+
108
104
  defn(:right_age, _) {
109
105
  false
110
106
  }
111
-
107
+
112
108
  defn(:wrong_age, _) {
113
109
  true
114
110
  }.when{|x| x < 16 || x > 104 }
115
-
111
+
116
112
  defn(:wrong_age, _) {
117
113
  false
118
114
  }
119
115
  end
120
116
 
117
+ class Baz < Foo
118
+ def boom_boom_room
119
+ 'zoom zoom zoom'
120
+ end
121
+ def who(first, last)
122
+ [first, last].join(' ')
123
+ end
124
+ end
125
+
126
+ class Fizzbuzz < Baz
127
+ include PatternMatching
128
+ defn(:who, Integer) { |count|
129
+ (1..count).each.reduce(:+)
130
+ }
131
+ defn(:who) { 0 }
132
+ end
133
+
134
+ describe 'integration' do
135
+
121
136
  let(:name) { 'Pattern Matcher' }
122
137
  subject { Foo.new(name) }
123
138
 
@@ -129,6 +144,9 @@ describe 'integration' do
129
144
  specify { subject.greet(:female, 'Jeri').should eq 'Hello, Ms. Jeri!' }
130
145
  specify { subject.greet(:unknown, 'Jerry').should eq 'Hello, Jerry!' }
131
146
  specify { subject.greet(nil, 'Jerry').should eq 'Goodbye, Jerry!' }
147
+ specify {
148
+ lambda { Foo.new.greet(1,2,3,4,5,6,7) }.should raise_error(NoMethodError)
149
+ }
132
150
 
133
151
  specify { subject.options(bar: :baz, one: 1, many: 2).should == {bar: :baz, one: 1, many: 2} }
134
152
 
@@ -167,4 +185,21 @@ describe 'integration' do
167
185
  specify { subject.wrong_age(10).should be_true }
168
186
  specify { subject.wrong_age(110).should be_true }
169
187
 
188
+ context 'inheritance' do
189
+
190
+ specify { Fizzbuzz.new.greet(:male, 'Jerry').should eq 'Hello, Mr. Jerry!' }
191
+ specify { Fizzbuzz.new.greet(:female, 'Jeri').should eq 'Hello, Ms. Jeri!' }
192
+ specify { Fizzbuzz.new.greet(:unknown, 'Jerry').should eq 'Hello, Jerry!' }
193
+ specify { Fizzbuzz.new.greet(nil, 'Jerry').should eq 'Goodbye, Jerry!' }
194
+
195
+ specify { Fizzbuzz.new.who(5).should eq 15 }
196
+ specify { Fizzbuzz.new.who().should eq 0 }
197
+ specify {
198
+ lambda {
199
+ Fizzbuzz.new.who('Jerry', 'secret middle name', "D'Antonio")
200
+ }.should raise_error(NoMethodError)
201
+ }
202
+
203
+ specify { Fizzbuzz.new.boom_boom_room.should eq 'zoom zoom zoom' }
204
+ end
170
205
  end
@@ -32,6 +32,21 @@ describe PatternMatching do
32
32
  end
33
33
  end
34
34
 
35
+ context 'constructor' do
36
+
37
+ it 'can pattern match the constructor' do
38
+
39
+ subject.defn(:initialize, PatternMatching::UNBOUND, PatternMatching::UNBOUND, PatternMatching::UNBOUND) { 'three args' }
40
+ subject.defn(:initialize, PatternMatching::UNBOUND, PatternMatching::UNBOUND) { 'two args' }
41
+ subject.defn(:initialize, PatternMatching::UNBOUND) { 'one arg' }
42
+
43
+ lambda { subject.new(1) }.should_not raise_error
44
+ lambda { subject.new(1, 2) }.should_not raise_error
45
+ lambda { subject.new(1, 2, 3) }.should_not raise_error
46
+ lambda { subject.new(1, 2, 3, 4) }.should raise_error
47
+ end
48
+ end
49
+
35
50
  context 'parameter count' do
36
51
 
37
52
  it 'does not match a call with not enough arguments' do
@@ -64,7 +79,7 @@ describe PatternMatching do
64
79
  end
65
80
  end
66
81
 
67
- class UnmatchedCallTesterSubclass < UnmatchedCallTesterSuperclass
82
+ class UnmatchedCallTesterSubclass < UnmatchedCallTesterSuperclass
68
83
  include PatternMatching
69
84
  defn(:foo) { 'baz' }
70
85
  end
@@ -89,7 +104,7 @@ describe PatternMatching do
89
104
  end
90
105
  end
91
106
 
92
- class RecursiveCallTesterSubclass < RecursiveCallTesterSuperclass
107
+ class RecursiveCallTesterSubclass < RecursiveCallTesterSuperclass
93
108
  include PatternMatching
94
109
  defn(:foo, :bar) { foo(:baz) }
95
110
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pattern-matching
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jerry D'Antonio
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-07 00:00:00.000000000 Z
11
+ date: 2013-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -45,7 +45,7 @@ homepage: https://github.com/jdantonio/pattern_matching/
45
45
  licenses:
46
46
  - MIT
47
47
  metadata: {}
48
- post_install_message: Happy matching!
48
+ post_install_message: start() -> io:format("Hello, World!").
49
49
  rdoc_options: []
50
50
  require_paths:
51
51
  - lib