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,17 @@
1
+ require 'spec_helper'
2
+
3
+ module Functional
4
+
5
+ context Configuration do
6
+
7
+ it 'configures the gem' do
8
+ cfg = nil
9
+ Functional.configure do |config|
10
+ cfg = config
11
+ end
12
+
13
+ expect(cfg).to be_a Configuration
14
+ expect(cfg).to eq Functional.configuration
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+
3
+ module Functional
4
+
5
+ describe Delay do
6
+
7
+ let!(:fulfilled_value) { 10 }
8
+ let!(:rejected_reason) { StandardError.new('mojo jojo') }
9
+
10
+ let(:pending_subject) do
11
+ Delay.new{ fulfilled_value }
12
+ end
13
+
14
+ let(:fulfilled_subject) do
15
+ delay = Delay.new{ fulfilled_value }
16
+ delay.tap{ delay.value }
17
+ end
18
+
19
+ let(:rejected_subject) do
20
+ delay = Delay.new{ raise rejected_reason }
21
+ delay.tap{ delay.value }
22
+ end
23
+
24
+ specify{ Functional::Protocol::Satisfy! Delay, :Disposition }
25
+
26
+ context '#initialize' do
27
+
28
+ it 'sets the state to :pending' do
29
+ expect(Delay.new{ nil }.state).to eq :pending
30
+ expect(Delay.new{ nil }).to be_pending
31
+ end
32
+
33
+ it 'raises an exception when no block given' do
34
+ expect {
35
+ Delay.new
36
+ }.to raise_error(ArgumentError)
37
+ end
38
+ end
39
+
40
+ context '#state' do
41
+
42
+ it 'is :pending when first created' do
43
+ f = pending_subject
44
+ expect(f.state).to eq(:pending)
45
+ expect(f).to be_pending
46
+ end
47
+
48
+ it 'is :fulfilled when the handler completes' do
49
+ f = fulfilled_subject
50
+ expect(f.state).to eq(:fulfilled)
51
+ expect(f).to be_fulfilled
52
+ end
53
+
54
+ it 'is :rejected when the handler raises an exception' do
55
+ f = rejected_subject
56
+ expect(f.state).to eq(:rejected)
57
+ expect(f).to be_rejected
58
+ end
59
+ end
60
+
61
+ context '#value' do
62
+
63
+ let(:task){ proc{ nil } }
64
+
65
+ it 'blocks the caller when :pending and timeout is nil' do
66
+ f = pending_subject
67
+ expect(f.value).to be_truthy
68
+ expect(f).to be_fulfilled
69
+ end
70
+
71
+ it 'is nil when :rejected' do
72
+ expected = rejected_subject.value
73
+ expect(expected).to be_nil
74
+ end
75
+
76
+ it 'is set to the return value of the block when :fulfilled' do
77
+ expected = fulfilled_subject.value
78
+ expect(expected).to eq fulfilled_value
79
+ end
80
+
81
+ it 'does not call the block before #value is called' do
82
+ expect(task).not_to receive(:call).with(any_args)
83
+ Delay.new(&task)
84
+ end
85
+
86
+ it 'calls the block when #value is called' do
87
+ expect(task).to receive(:call).once.with(any_args).and_return(nil)
88
+ Delay.new(&task).value
89
+ end
90
+
91
+ it 'only calls the block once no matter how often #value is called' do
92
+ expect(task).to receive(:call).once.with(any_args).and_return(nil)
93
+ delay = Delay.new(&task)
94
+ 5.times{ delay.value }
95
+ end
96
+ end
97
+
98
+ context '#reason' do
99
+
100
+ it 'is nil when :pending' do
101
+ expect(pending_subject.reason).to be_nil
102
+ end
103
+
104
+ it 'is nil when :fulfilled' do
105
+ expect(fulfilled_subject.reason).to be_nil
106
+ end
107
+
108
+ it 'is set to error object of the exception when :rejected' do
109
+ expect(rejected_subject.reason).to be_a(Exception)
110
+ expect(rejected_subject.reason.to_s).to match(/#{rejected_reason}/)
111
+ end
112
+ end
113
+
114
+ context 'predicates' do
115
+
116
+ specify '#value? returns true when :fulfilled' do
117
+ expect(pending_subject).to_not be_value
118
+ expect(fulfilled_subject).to be_value
119
+ expect(rejected_subject).to_not be_value
120
+ end
121
+
122
+ specify '#reason? returns true when :rejected' do
123
+ expect(pending_subject).to_not be_reason
124
+ expect(fulfilled_subject).to_not be_reason
125
+ expect(rejected_subject).to be_reason
126
+ end
127
+
128
+ specify '#fulfilled? returns true when :fulfilled' do
129
+ expect(pending_subject).to_not be_fulfilled
130
+ expect(fulfilled_subject).to be_fulfilled
131
+ expect(rejected_subject).to_not be_fulfilled
132
+ end
133
+
134
+ specify '#rejected? returns true when :rejected' do
135
+ expect(pending_subject).to_not be_rejected
136
+ expect(fulfilled_subject).to_not be_rejected
137
+ expect(rejected_subject).to be_rejected
138
+ end
139
+
140
+ specify '#pending? returns true when :pending' do
141
+ expect(pending_subject).to be_pending
142
+ expect(fulfilled_subject).to_not be_pending
143
+ expect(rejected_subject).to_not be_pending
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,237 @@
1
+ require 'spec_helper'
2
+ require_relative 'abstract_struct_shared'
3
+
4
+ module Functional
5
+
6
+ describe Either do
7
+
8
+ let!(:value){ 42 }
9
+ let!(:reason){ StandardError.new }
10
+
11
+ let!(:expected_fields){ [:left, :right] }
12
+ let!(:expected_values){ [value, nil] }
13
+
14
+ let(:struct_class) { Either }
15
+ let(:struct_object) { Either.left(value) }
16
+ let(:other_object) { Either.left(Object.new) }
17
+
18
+ let(:left_subject){ Either.left(reason) }
19
+ let(:right_subject){ Either.right(value) }
20
+
21
+ it_should_behave_like :abstract_struct
22
+
23
+ specify{ Functional::Protocol::Satisfy! Either, :Either }
24
+ specify{ Functional::Protocol::Satisfy! Either, :Disposition }
25
+
26
+ context 'initialization' do
27
+
28
+ it 'cannot be constructed directly' do
29
+ expect {
30
+ Either.new
31
+ }.to raise_error(NameError)
32
+ end
33
+
34
+ it 'sets the left value when constructed by #left' do
35
+ expect(Either.left(value).left).to eq value
36
+ end
37
+
38
+ it 'sets the right value when constructed by #right' do
39
+ expect(Either.right(value).right).to eq value
40
+ end
41
+
42
+ it 'freezes the new object' do
43
+ expect(Either.left(:foo)).to be_frozen
44
+ expect(Either.right(:foo)).to be_frozen
45
+ end
46
+
47
+ it 'aliases #left to #reason' do
48
+ expect(Either.reason(value).left).to eq value
49
+ end
50
+
51
+ it 'aliases #right to #value' do
52
+ expect(Either.value(value).right).to eq value
53
+ end
54
+
55
+ context '#error' do
56
+
57
+ it 'sets left to a StandardError with backtrace when no arguments given' do
58
+ either = Either.error
59
+ expect(either.left).to be_a StandardError
60
+ expect(either.left.message).to_not be nil
61
+ expect(either.left.backtrace).to_not be_empty
62
+ end
63
+
64
+ it 'sets left to a StandardError with the given message' do
65
+ message = 'custom error message'
66
+ either = Either.error(message)
67
+ expect(either.left).to be_a StandardError
68
+ expect(either.left.message).to eq message
69
+ expect(either.left.backtrace).to_not be_empty
70
+ end
71
+
72
+ it 'sets left to an object of the given class with the given message' do
73
+ message = 'custom error message'
74
+ error_class = ArgumentError
75
+ either = Either.error(message, error_class)
76
+ expect(either.left).to be_a error_class
77
+ expect(either.left.message).to eq message
78
+ expect(either.left.backtrace).to_not be_empty
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'state' do
84
+
85
+ specify '#left? returns true when the left value is set' do
86
+ expect(left_subject).to be_left
87
+ end
88
+
89
+ specify '#left? returns false when the right value is set' do
90
+ expect(right_subject).to_not be_left
91
+ end
92
+
93
+ specify '#right? returns true when the right value is set' do
94
+ expect(right_subject).to be_right
95
+ end
96
+
97
+ specify '#right? returns false when the left value is set' do
98
+ expect(left_subject).to_not be_right
99
+ end
100
+
101
+ specify '#left returns the left value when left is set' do
102
+ expect(left_subject.left).to eq reason
103
+ end
104
+
105
+ specify '#left returns nil when right is set' do
106
+ expect(right_subject.left).to be_nil
107
+ end
108
+
109
+ specify '#right returns the right value when right is set' do
110
+ expect(right_subject.right).to eq value
111
+ end
112
+
113
+ specify '#right returns nil when left is set' do
114
+ expect(left_subject.right).to be_nil
115
+ end
116
+
117
+ specify 'aliases #left? as #reason?' do
118
+ expect(left_subject.reason?).to be true
119
+ end
120
+
121
+ specify 'aliases #right? as #value?' do
122
+ expect(right_subject.value?).to be true
123
+ end
124
+
125
+ specify 'aliases #left as #reason' do
126
+ expect(left_subject.reason).to eq reason
127
+ expect(right_subject.reason).to be_nil
128
+ end
129
+
130
+ specify 'aliases #right as #value' do
131
+ expect(right_subject.value).to eq value
132
+ expect(left_subject.value).to be_nil
133
+ end
134
+ end
135
+
136
+ context '#swap' do
137
+
138
+ it 'converts a left projection into a right projection' do
139
+ subject = Either.left(:foo)
140
+ swapped = subject.swap
141
+ expect(swapped).to be_right
142
+ expect(swapped.left).to be_nil
143
+ expect(swapped.right).to eq :foo
144
+ end
145
+
146
+ it 'converts a right projection into a left projection' do
147
+ subject = Either.right(:foo)
148
+ swapped = subject.swap
149
+ expect(swapped).to be_left
150
+ expect(swapped.right).to be_nil
151
+ expect(swapped.left).to eq :foo
152
+ end
153
+ end
154
+
155
+ context '#either' do
156
+
157
+ it 'passes the left value to the left proc when left' do
158
+ expected = nil
159
+ subject = Either.left(100)
160
+ subject.either(
161
+ ->(left) { expected = left },
162
+ ->(right) { expected = -1 }
163
+ )
164
+ expect(expected).to eq 100
165
+ end
166
+
167
+ it 'returns the value of the left proc when left' do
168
+ subject = Either.left(100)
169
+ expect(
170
+ subject.either(
171
+ ->(left) { left * 2 },
172
+ ->(right) { nil }
173
+ )
174
+ ).to eq 200
175
+ end
176
+
177
+ it 'passes the right value to the right proc when right' do
178
+ expected = nil
179
+ subject = Either.right(100)
180
+ subject.either(
181
+ ->(right) { expected = -1 },
182
+ ->(right) { expected = right }
183
+ )
184
+ expect(expected).to eq 100
185
+ end
186
+
187
+ it 'returns the value of the right proc when right' do
188
+ subject = Either.right(100)
189
+ expect(
190
+ subject.either(
191
+ ->(right) { nil },
192
+ ->(right) { right * 2 }
193
+ )
194
+ ).to eq 200
195
+ end
196
+ end
197
+
198
+ context '#iff' do
199
+
200
+ it 'returns a lefty with the given left value when the boolean is true' do
201
+ subject = Either.iff(:foo, :bar, true)
202
+ expect(subject).to be_left
203
+ expect(subject.left).to eq :foo
204
+ end
205
+
206
+ it 'returns a righty with the given right value when the boolean is false' do
207
+ subject = Either.iff(:foo, :bar, false)
208
+ expect(subject).to be_right
209
+ expect(subject.right).to eq :bar
210
+ end
211
+
212
+ it 'returns a lefty with the given left value when the block is truthy' do
213
+ subject = Either.iff(:foo, :bar){ :baz }
214
+ expect(subject).to be_left
215
+ expect(subject.left).to eq :foo
216
+ end
217
+
218
+ it 'returns a righty with the given right value when the block is false' do
219
+ subject = Either.iff(:foo, :bar){ false }
220
+ expect(subject).to be_right
221
+ expect(subject.right).to eq :bar
222
+ end
223
+
224
+ it 'returns a righty with the given right value when the block is nil' do
225
+ subject = Either.iff(:foo, :bar){ nil }
226
+ expect(subject).to be_right
227
+ expect(subject.right).to eq :bar
228
+ end
229
+
230
+ it 'raises an exception when both a boolean and a block are given' do
231
+ expect {
232
+ subject = Either.iff(:foo, :bar, true){ nil }
233
+ }.to raise_error(ArgumentError)
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,207 @@
1
+ require 'spec_helper'
2
+
3
+ module Functional
4
+
5
+ describe Memo do
6
+
7
+ def create_new_memo_class
8
+ Class.new do
9
+ include Functional::Memo
10
+
11
+ class << self
12
+ attr_accessor :count
13
+ end
14
+
15
+ self.count = 0
16
+
17
+ def self.add(a, b)
18
+ self.count += 1
19
+ a + b
20
+ end
21
+ memoize :add
22
+
23
+ def self.increment(n)
24
+ self.count += 1
25
+ end
26
+
27
+ def self.exception(ex = StandardError)
28
+ raise ex
29
+ end
30
+ end
31
+ end
32
+
33
+ subject{ create_new_memo_class }
34
+
35
+ context 'specification' do
36
+
37
+ it 'raises an exception when the method is not defined' do
38
+ expect {
39
+ subject.memoize(:bogus)
40
+ }.to raise_error(NameError)
41
+ end
42
+
43
+ it 'raises an exception when the given method has already been memoized' do
44
+ expect{
45
+ subject.memoize(:add)
46
+ }.to raise_error(ArgumentError)
47
+ end
48
+
49
+ it 'allocates a different cache for each class/module' do
50
+ class_1 = create_new_memo_class
51
+ class_2 = create_new_memo_class
52
+
53
+ 10.times do
54
+ class_1.add(0, 0)
55
+ class_2.add(0, 0)
56
+ end
57
+
58
+ expect(class_1.count).to eq 1
59
+ expect(class_2.count).to eq 1
60
+ end
61
+
62
+ it 'works when included in a class' do
63
+ subject = Class.new do
64
+ include Functional::Memo
65
+ class << self
66
+ attr_accessor :count
67
+ end
68
+ self.count = 0
69
+ def self.foo
70
+ self.count += 1
71
+ end
72
+ memoize :foo
73
+ end
74
+
75
+ 10.times{ subject.foo }
76
+ expect(subject.count).to eq 1
77
+ end
78
+
79
+ it 'works when included in a module' do
80
+ subject = Module.new do
81
+ include Functional::Memo
82
+ class << self
83
+ attr_accessor :count
84
+ end
85
+ self.count = 0
86
+ def self.foo
87
+ self.count += 1
88
+ end
89
+ memoize :foo
90
+ end
91
+
92
+ 10.times{ subject.foo }
93
+ expect(subject.count).to eq 1
94
+ end
95
+
96
+ it 'works when extended by a module' do
97
+ subject = Module.new do
98
+ extend Functional::Memo
99
+ class << self
100
+ attr_accessor :count
101
+ end
102
+ self.count = 0
103
+ def self.foo
104
+ self.count += 1
105
+ end
106
+ memoize :foo
107
+ end
108
+
109
+ 10.times{ subject.foo }
110
+ expect(subject.count).to eq 1
111
+ end
112
+ end
113
+
114
+ context 'caching behavior' do
115
+
116
+ it 'calls the real method on first instance of given args' do
117
+ subject.add(1, 2)
118
+ expect(subject.count).to eq 1
119
+ end
120
+
121
+ it 'calls the real method on first instance of given args' do
122
+ subject.add(1, 2)
123
+ expect(subject.count).to eq 1
124
+ end
125
+
126
+ it 'uses the memo on second instance of given args' do
127
+ 5.times { subject.add(1, 2) }
128
+ expect(subject.count).to eq 1
129
+ end
130
+
131
+ it 'calls the real method when given a block' do
132
+ 5.times { subject.add(1, 2){ nil } }
133
+ expect(subject.count).to eq 5
134
+ end
135
+
136
+ it 'raises an exception when arity does not match' do
137
+ expect {
138
+ subject.add
139
+ }.to raise_error(ArgumentError)
140
+ end
141
+ end
142
+
143
+ context 'maximum cache size' do
144
+
145
+ it 'raises an exception when given a non-positive :at_most' do
146
+ expect {
147
+ subject.memoize(:increment, at_most: -1)
148
+ }.to raise_error(ArgumentError)
149
+ end
150
+
151
+ it 'sets no limit when :at_most not given' do
152
+ subject.memoize(:increment)
153
+ 10000.times{|i| subject.increment(i) }
154
+ expect(subject.count).to eq 10000
155
+ end
156
+
157
+ it 'calls the real method when the :at_most size is reached' do
158
+ subject.memoize(:increment, at_most: 5)
159
+ 10000.times{|i| subject.increment(i % 10) }
160
+ expect(subject.count).to eq 5005
161
+ end
162
+ end
163
+
164
+ context 'thread safety' do
165
+
166
+ let(:mutex){ Mutex.new }
167
+
168
+ before(:each) do
169
+ allow(Mutex).to receive(:new).with(no_args).and_return(mutex)
170
+ allow(mutex).to receive(:lock).with(no_args).and_return(mutex)
171
+ allow(mutex).to receive(:unlock).with(no_args).and_return(mutex)
172
+ end
173
+
174
+ it 'locks a mutex whenever a memoized function is called' do
175
+ expect(mutex).to receive(:lock).exactly(:once).with(no_args)
176
+
177
+ subject.memoize(:increment)
178
+ subject.increment(0)
179
+ end
180
+
181
+ it 'unlocks the mutex whenever a memoized function is called' do
182
+ expect(mutex).to receive(:unlock).exactly(:once).with(no_args)
183
+
184
+ subject.memoize(:increment)
185
+ subject.increment(0)
186
+ end
187
+
188
+ it 'unlocks the mutex when the method call raises an exception' do
189
+ expect(mutex).to receive(:unlock).exactly(:once).with(no_args)
190
+
191
+ subject.memoize(:exception)
192
+ begin
193
+ subject.exception
194
+ rescue
195
+ # suppress
196
+ end
197
+ end
198
+
199
+ it 'uses different mutexes for different functions' do
200
+ expect(Mutex).to receive(:new).with(no_args).exactly(3).times.and_return(mutex)
201
+ # once for memoize(:add) in the definition
202
+ subject.memoize(:increment)
203
+ subject.memoize(:exception)
204
+ end
205
+ end
206
+ end
207
+ end