totally_lazy 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sequence.rb CHANGED
@@ -6,9 +6,30 @@ end
6
6
 
7
7
  module Sequences
8
8
 
9
+ # Creates a sequence
10
+ #
11
+ # == Parameters:
12
+ # items::
13
+ # Varargs - any valid ruby objects can be supplied
14
+ #
15
+ # == Returns:
16
+ # A sequence
17
+ #
18
+ # == Examples
19
+ #
20
+ # sequence(1,2,3,4).filter(even) # lazily returns 2,4
21
+ # sequence(1,2).map(as_string) # lazily returns "1","2"
22
+ # sequence(1, 2).map_concurrently(to_string) # lazily distributes the work to background threads
23
+ # sequence(1,2,3).take(2) # lazily returns 1,2
24
+ # sequence(1,2,3).drop(2) # lazily returns 3
25
+ # sequence(1,2,3).tail # lazily returns 2,3
26
+ # sequence(1,2,3).head # eagerly returns 1
27
+ # sequence(1,2,3).head_option # eagerly returns an option
28
+ # some(sequence(1,2,3)).get_or_else(empty) # eagerly returns value or else empty sequence
29
+ # sequence(1, 2, 3, 4, 5).filter(where(is greater_than 2).and(is odd)) # lazily returns 3,5
9
30
  def sequence(*items)
10
31
  if items.size == 1
11
- if [Range, Hash].include?(items.first.class)
32
+ if [Range, Hash, Array, Set].include?(items.first.class)
12
33
  Sequence.new(items.first)
13
34
  elsif items.first.nil?
14
35
  empty
@@ -20,10 +41,23 @@ module Sequences
20
41
  end
21
42
  end
22
43
 
44
+ # Creates an empty sequence
45
+ #
46
+ # == Returns:
47
+ # An empty sequence
48
+ #
49
+ # == Examples
50
+ #
51
+ # empty
52
+ # some(sequence(1,2,3)).get_or_else(empty) # eagerly returns value or else empty sequence
23
53
  def empty
24
54
  Empty.new
25
55
  end
26
56
 
57
+ def deserialize(data)
58
+ sequence(data).deserialize
59
+ end
60
+
27
61
  class Sequence < Enumerator
28
62
 
29
63
  include Comparable
@@ -50,7 +84,7 @@ module Sequences
50
84
  def map(predicate=nil, &block)
51
85
  if predicate
52
86
  Sequence.new(self) { |yielder, val|
53
- v = predicate.call(val)
87
+ v = predicate.is_a?(WherePredicate) ? WhereProcessor.new(val).apply(predicate.predicates) : predicate.call(val)
54
88
  yielder << v unless v.nil?
55
89
  }
56
90
  else
@@ -62,10 +96,32 @@ module Sequences
62
96
 
63
97
  alias collect map
64
98
 
99
+ # def reduce(operation_or_value=nil)
100
+ # case operation_or_value
101
+ # when Symbol
102
+ # # convert things like reduce(:+) into reduce { |s,e| s + e }
103
+ # return reduce { |s,e| s.send(operation_or_value, e) }
104
+ # when nil
105
+ # acc = nil
106
+ # else
107
+ # acc = operation_or_value
108
+ # end
109
+ #
110
+ # each do |a|
111
+ # if acc.nil?
112
+ # acc = a
113
+ # else
114
+ # acc = yield(acc, a)
115
+ # end
116
+ # end
117
+ #
118
+ # return acc
119
+ # end
120
+
65
121
  def select(predicate=nil, &block)
66
122
  if predicate
67
123
  Sequence.new(self) { |yielder, val|
68
- v = predicate.call(val)
124
+ v = predicate.is_a?(WherePredicate) ? WhereProcessor.new(val).apply(predicate.predicates) : predicate.call(val)
69
125
  yielder << v unless v.nil?
70
126
  }
71
127
  else
@@ -80,14 +136,23 @@ module Sequences
80
136
  alias find_all select
81
137
  alias filter select
82
138
 
83
- def reject(&block)
84
- Sequence.new(self) { |yielder, val|
85
- if not block.call(val)
86
- yielder << val
87
- end
88
- }
139
+ def reject(predicate=nil, &block)
140
+ if predicate
141
+ Sequence.new(self) { |yielder, val|
142
+ v = predicate.is_a?(WherePredicate) ? WhereProcessor.new(val).apply(predicate.predicates, true) : predicate.call(val, :self, true)
143
+ yielder << v unless v.nil?
144
+ }
145
+ else
146
+ Sequence.new(self) { |yielder, val|
147
+ unless block.call(val)
148
+ yielder << val
149
+ end
150
+ }
151
+ end
89
152
  end
90
153
 
154
+ alias unfilter reject
155
+
91
156
  def grep(pattern)
92
157
  Sequence.new(self) { |yielder, val|
93
158
  if pattern === val
@@ -146,7 +211,6 @@ module Sequences
146
211
  def flat_map(&block)
147
212
  Sequence.new(self) { |yielder, val|
148
213
  ary = block.call(val)
149
- # TODO: check ary is an Array
150
214
  ary.each { |x|
151
215
  yielder << x
152
216
  }
@@ -197,6 +261,10 @@ module Sequences
197
261
  sequence.empty? ? none : some(sequence.entries.last)
198
262
  end
199
263
 
264
+ def contains?(value)
265
+ sequence.empty? ? false : sequence.entries.include?(value)
266
+ end
267
+
200
268
  def tail
201
269
  Sequence.new(Sequence::Generator.new do |g|
202
270
  self.empty? ? raise(NoSuchElementException.new, 'The sequence was empty') : self.drop(1).each { |i| g.yield i }
@@ -218,18 +286,19 @@ module Sequences
218
286
 
219
287
  def transpose
220
288
  Sequence.new(Sequence::Generator.new do |g|
289
+ if self.empty?
290
+ raise(NoSuchElementException.new, 'The sequence was empty')
291
+ else
292
+ raise(Exception.new, 'The subject of transposition must be multidimensional') unless self.to_a.first.is_a?(Array)
293
+ end
221
294
  result = []
222
- max_size = self.to_a.max { |a, b| a.size <=> b.size }.size
295
+ max = option(self.to_a.max { |a, b| a.size <=> b.size })
296
+ max_size = max.get_or_throw(NoSuchElementException, 'The option was empty').size
223
297
  max_size.times do |i|
224
298
  result[i] = [self.to_a.first.size]
225
299
  self.to_a.each_with_index { |r, j| result[i][j] = r[i] }
226
300
  end
227
301
  result
228
- if self.empty?
229
- raise(NoSuchElementException.new, 'The sequence was empty')
230
- else
231
- raise(Exception.new, 'The subject of transposition must be multidimensional') unless self.to_a.first.is_a?(Array)
232
- end
233
302
  result.each { |i| g.yield i }
234
303
  end)
235
304
  end
@@ -237,9 +306,7 @@ module Sequences
237
306
  def join(target_sequence)
238
307
  Sequence.new(Sequence::Generator.new do |g|
239
308
  raise(Exception.new, 'The target (right side) must be a sequence') unless target_sequence.kind_of?(Sequences::Sequence)
240
- elements = self.entries
241
- elements = elements.reject { |i| i.is_a?(Empty) }
242
- (elements << target_sequence.entries).flatten.each { |i| g.yield i }
309
+ self.entries.push(target_sequence.entries).flatten.each { |i| g.yield i unless i.is_a?(Empty) }
243
310
  end)
244
311
  end
245
312
 
@@ -263,7 +330,7 @@ module Sequences
263
330
 
264
331
  def to_seq
265
332
  Sequence.new(Sequence::Generator.new do |g|
266
- self.entries.map { |e| Type.check(e, Sequences::Sequence); e.entries }.flatten.each { |i| g.yield i }
333
+ self.entries.map { |e| Type.responds(e, :entries); e.entries }.flatten.each { |i| g.yield i }
267
334
  end)
268
335
  end
269
336
 
@@ -273,6 +340,24 @@ module Sequences
273
340
  end)
274
341
  end
275
342
 
343
+ def from_arrays
344
+ Sequence.new(Sequence::Generator.new do |g|
345
+ self.entries.map { |e| Type.check(e, Array); e }.flatten.each { |i| g.yield i }
346
+ end)
347
+ end
348
+
349
+ def from_sets
350
+ Sequence.new(Sequence::Generator.new do |g|
351
+ self.entries.map { |e| Type.check(e, Set); e.to_a }.flatten.each { |i| g.yield i }
352
+ end)
353
+ end
354
+
355
+ def in_pairs
356
+ Sequence.new(Sequence::Generator.new do |g|
357
+ self.each_slice(2) { |k, v| g.yield pair(k, v) }
358
+ end)
359
+ end
360
+
276
361
  def to_a
277
362
  execution = {
278
363
  Sequences::Sequence => -> { self.entries.map { |s| s.entries } },
@@ -287,6 +372,26 @@ module Sequences
287
372
  end
288
373
  end
289
374
 
375
+ def marshal_dump
376
+ serialize
377
+ end
378
+
379
+ def marshal_load(data)
380
+ Sequence.new(data).deserialize
381
+ end
382
+
383
+ def serialize
384
+ c = []
385
+ serializer(c, self.entries)
386
+ c
387
+ end
388
+
389
+ def deserialize
390
+ c = []
391
+ deserializer(c, self.entries)
392
+ Sequence.new(c)
393
+ end
394
+
290
395
  def all
291
396
  to_a.flatten
292
397
  end
@@ -299,8 +404,42 @@ module Sequences
299
404
  blank?(sequence[index]) ? none : some(sequence[index])
300
405
  end
301
406
 
407
+ def get_or_throw(index, exception, message='')
408
+ blank?(sequence[index]) ? raise(exception, message) : sequence[index]
409
+ end
410
+
411
+ def drop_nil
412
+ Sequence.new(Sequence::Generator.new do |g|
413
+ self.reject { |e| e.nil? }.each { |i| g.yield i }
414
+ end)
415
+ end
416
+
417
+ def map_concurrently(predicate=nil, options={}, &block)
418
+ if predicate
419
+ Sequence.new(Sequence::Generator.new do |g|
420
+ Parallel.map(self.entries, options) { |val|
421
+ predicate.is_a?(WherePredicate) ? WhereProcessor.new(val).apply(predicate.predicates) : predicate.call(val)
422
+ }.each { |i| g.yield i unless i.nil? }
423
+ end)
424
+ else
425
+ Sequence.new(Sequence::Generator.new do |g|
426
+ Parallel.map(self.entries, options) { |val| block.call(val) }.each { |i| g.yield i }
427
+ end)
428
+ end
429
+ end
430
+
431
+ def each_concurrently(options={}, &block)
432
+ Parallel.each(self.entries, options) { |val| block.call(val) }
433
+ end
434
+
435
+ def cycle
436
+ Sequence.new(Sequence::Generator.new do |g|
437
+ self.entries.cycle.each { |i| g.yield i }
438
+ end)
439
+ end
440
+
441
+ protected
302
442
 
303
- private
304
443
  def sequence
305
444
  Sequence.new(self)
306
445
  end
@@ -309,6 +448,63 @@ module Sequences
309
448
  item.respond_to?(:empty?) ? item.empty? : !item
310
449
  end
311
450
 
451
+ def serializer(container, entries)
452
+ entries.each do |entry|
453
+ if entry.is_a?(Sequences::Sequence)
454
+ data = []
455
+ serializer(data, entry)
456
+ container << {type: :sequence, values: data}
457
+ elsif entry.is_a?(Pair::Pair)
458
+ if entry.second.is_a?(Sequences::Sequence)
459
+ data = []
460
+ serializer(data, entry)
461
+ container << {type: :pair, values: data}
462
+ else
463
+ container << {type: :pair, values: entry.to_map}
464
+ end
465
+ elsif entry.is_a?(Option::Some)
466
+ container << {type: :some, values: entry.value}
467
+ elsif entry.is_a?(Option::None)
468
+ container << {type: :none, values: nil}
469
+ elsif entry.respond_to?(:each)
470
+ data = []
471
+ serializer(data, entry)
472
+ container << {type: entry.class, values: data}
473
+ else
474
+ container << entry
475
+ end
476
+ end
477
+ end
478
+
479
+ def deserializer(container, data)
480
+ data.each do |entry|
481
+ if entry.is_a?(Hash)
482
+ if entry[:type] == :sequence
483
+ data = []
484
+ deserializer(data, entry[:values])
485
+ container << Sequence.new(data)
486
+ elsif entry[:type] == :pair
487
+ if entry[:values].is_a?(Array)
488
+ container << pair(entry[:values].first, Sequence.new(entry[:values][1][:values]))
489
+ else
490
+ container << pair(entry[:values].keys.first, entry[:values].values.first)
491
+ end
492
+ elsif entry[:type] == :some
493
+ container << some(entry[:values])
494
+ elsif entry[:type] == :none
495
+ container << none
496
+ elsif entry[:type] == Hash
497
+ h = entry[:values].map { |e| [e[:values]].to_h }.reduce({}) { |a, b| a.merge(b) }
498
+ container << h
499
+ else
500
+ container << entry[:type].send(:new, entry[:values])
501
+ end
502
+ else
503
+ container << entry
504
+ end
505
+ end
506
+ end
507
+
312
508
  end
313
509
 
314
510
  class Empty < Sequence
data/lib/totally_lazy.rb CHANGED
@@ -1,15 +1,138 @@
1
1
  require_relative 'type_check'
2
+ require 'set'
3
+ require 'prime'
2
4
  require_relative 'sequence'
3
5
  require_relative 'pair'
4
6
  require_relative 'option'
5
7
  require_relative 'functor'
6
- require_relative 'predicates'
8
+ require_relative 'predicates/predicates'
9
+ require_relative 'predicates/compare'
10
+ require_relative 'predicates/conversions'
11
+ require_relative 'predicates/numbers'
12
+ require_relative 'predicates/where'
13
+ require_relative 'predicates/where_processor'
14
+ require_relative 'parallel/parallel'
15
+ require_relative 'generators'
16
+ require_relative 'any'
7
17
 
8
18
  include Sequences
9
19
  include Option
10
20
  include Pair
21
+ include Predicates
11
22
  include Predicates::Numbers
12
23
  include Predicates::Conversions
24
+ include Predicates::Compare
25
+ include Predicates::Where
26
+ include Generators
27
+ include Any
28
+
29
+
30
+ # p sequence(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12).filter(
31
+ # where( is(greater_than 2).or(is equal_to 5) )
32
+ # ).entries
33
+ #
34
+ # sequence.filter(where.is(greater_than 2))
35
+
36
+
37
+
38
+ # p sequence(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12).filter(where(is greater_than 2).or(is less_than 8 ).and(is odd)).entries
39
+
40
+ # p sequence(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12).filter(where(is greater_than 2).and(is less_than 8 ).and(is even)).entries
41
+
42
+ # p sequence(pair(1, 2), pair(3, 4)).filter(where(key:odd)).entries
43
+
44
+ # p sequence(1,2,3,4,5,6).map_concurrently(as_string,in_threads:10).entries
45
+ # p sequence(1,2,3,4,5,6).map_concurrently{|i| i+5 }.entries
46
+
47
+ # sequence(1,2,3,4,5).each_concurrently{|i| sleep rand(10); p i }
48
+
49
+
50
+
51
+ # p Seq.repeat('car').take(10).entries
52
+ # p Seq.range(1,1000000000000000000000000000).take(20).entries
53
+
54
+ # p Seq.iterate(:*,2).take(10).entries
55
+ # p Seq.fibonacci.take(20).entries
56
+ # p Iter.fibonacci.take(20).entries
57
+ # p Iter.primes.take(10).entries
58
+
59
+ # p Seq.powers_of(3).take(10).entries
60
+ # p Iter.powers_of(3).take(10).entries
61
+
62
+ # p Seq.iterate(:+,1,0).take(10).entries
63
+
64
+ # p Seq.range(1,4).cycle.take(20).entries
65
+ # p Iter.range(1,4).cycle.take(20).entries
66
+
67
+ # p Seq.iterate(:+, 1).filter(even).take(2).reduce(:+)
68
+
69
+ #
70
+ # class Person
71
+ # attr_accessor :first_name,:last_name,:age
72
+ # def initialize(first_name,last_name,age)
73
+ # @first_name = first_name
74
+ # @last_name = last_name
75
+ # @age = age
76
+ # end
77
+ #
78
+ # def name
79
+ # @first_name
80
+ # end
81
+ #
82
+ # end
83
+ #
84
+ # people = Seq.repeat(-> {Person.new(Any.string,Any.string,Any.string)}).take(10)
85
+ #
86
+ # p people.filter(where(first_name:matches(/s/))).map(&:first_name).entries
87
+
88
+ # p sequence(pair('apples','pairs'),pair('banana','orange'),pair('apples','melon')).filter(where key:matches(/app/)).entries
89
+
90
+ # p Seq.repeat(-> { rand(9)}).take(10).to_a.join.to_i
91
+
92
+ # p Seq.range(1,100000000000000).shuffle.take(10).to_a
93
+
94
+ # a = sequence(pair(7,8),sequence(1,2),sequence(3,sequence(5,6)),sequence(pair(:apple,99), option(1),none),[10,11],{:apple => 8,:pear => 9}).serialize
95
+ #
96
+ # p a
97
+ # p deserialize(a)[5]
98
+
99
+ # p sequence(pair(1,2))[0]
100
+
101
+ # p empty.gan(ser).entries
102
+
103
+ # def countdown(c,n)
104
+ # return if n.zero? # base case
105
+ # c << n
106
+ # countdown(c,n-1) # getting closer to base case
107
+ # end
108
+ #
109
+ #
110
+ # a = []
111
+ # countdown(a,5)
112
+ # p a
113
+
114
+
115
+ # s1 = Seq.range(1,3000000)
116
+ # s2 = Seq.range(1,3000000)
117
+ #
118
+ # s = Time.now
119
+ # p s1.join2(s2).head
120
+ # f = Time.now
121
+ # p f - s
122
+ #
123
+ #
124
+ # s = Time.now
125
+ # p s1.join(s2).head
126
+ # f = Time.now
127
+ # p f - s
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
13
136
 
14
137
 
15
138
 
@@ -0,0 +1,35 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe 'Functor' do
4
+
5
+ it 'should create a new functor' do
6
+ fc = Functor.new do |op, *a, &b|
7
+ [op, a, b]
8
+ end
9
+ expect(fc+1).to eq([:+, [1], nil])
10
+ end
11
+
12
+ it 'should support class method' do
13
+ fc = Functor.new do |op, *a, &b|
14
+ [op, a, b]
15
+ end
16
+ expect( fc.__class__).to eq(Functor)
17
+ end
18
+
19
+ it 'should support to proc' do
20
+ f = Functor.new do |op, *a|
21
+ [op, *a]
22
+ end
23
+ p = f.to_proc
24
+ expect(p.class).to eq(Proc)
25
+ expect(p.call(:+,1,2,3)).to eq([:+,1,2,3])
26
+ end
27
+
28
+ it 'should cache a new functor' do
29
+ Functor.cache(:cached) do |op, *a, &b|
30
+ [op, a, b]
31
+ end
32
+ expect(Functor.cache(:cached) + 1).to eq([:+, [1], nil])
33
+ end
34
+
35
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe 'Generators' do
4
+
5
+ it 'should support repeat' do
6
+ expect(Seq.repeat('car').take(2)).to eq(sequence('car', 'car'))
7
+ expect(Iter.repeat('car').take(2).entries).to eq(%w(car car))
8
+ end
9
+
10
+ it 'should support range' do
11
+ expect(Seq.range(1, 4)).to eq(sequence(1, 2, 3, 4))
12
+ expect(Iter.range(1, 4).entries).to eq([1, 2, 3, 4])
13
+ end
14
+
15
+ it 'should support iterate' do
16
+ expect(Seq.iterate(:+, 1).take(4)).to eq(sequence(1, 2, 3, 4))
17
+ expect(Iter.iterate(:+, 1).take(4).entries).to eq([1, 2, 3, 4])
18
+ expect(Seq.iterate(:+, 1, 5).take(4)).to eq(sequence(5, 6, 7, 8))
19
+ expect(Iter.iterate(:+, 1, 5).take(4).entries).to eq([5, 6, 7, 8])
20
+ end
21
+
22
+ it 'should support primes' do
23
+ expect(Seq.primes.take(3)).to eq(sequence(2, 3, 5))
24
+ expect(Iter.primes.take(3).entries).to eq([2, 3, 5])
25
+ end
26
+
27
+ it 'should support fibonacci' do
28
+ expect(Seq.fibonacci.take(3)).to eq(sequence(1, 1, 2))
29
+ expect(Iter.fibonacci.take(3).entries).to eq([1, 1, 2])
30
+ end
31
+
32
+ it 'should support powers_of' do
33
+ expect(Seq.range(1, 4)).to eq(sequence(1, 2, 3, 4))
34
+ expect(Iter.range(1, 4).entries).to eq([1, 2, 3, 4])
35
+ end
36
+
37
+ end
data/spec/option_spec.rb CHANGED
@@ -1,5 +1,4 @@
1
- require 'rspec'
2
- require_relative '../lib/totally_lazy'
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
2
 
4
3
  describe 'Option' do
5
4
 
@@ -45,5 +44,77 @@ describe 'Option' do
45
44
  expect { option(empty).get_or_throw(Exception) }.to raise_error(Exception)
46
45
  end
47
46
 
47
+ it 'should return boolean for empty?' do
48
+ expect(option(1).empty?).to eq(false)
49
+ expect(option(empty).empty?).to eq(true)
50
+ end
51
+
52
+ it 'should return boolean for defined?' do
53
+ expect(option(1).defined?).to eq(true)
54
+ expect(option('').defined?).to eq(false)
55
+ end
56
+
57
+ it 'should convert to sequence' do
58
+ expect(option(1).to_seq).to eq(sequence(option(1)))
59
+ end
60
+
61
+ it 'should raise empty exception when calling get on none' do
62
+ expect { option(empty).get }.to raise_error(NoSuchElementException)
63
+ end
64
+
65
+ it 'should raise empty exception when calling value on none' do
66
+ expect { option(empty).value }.to raise_error(NoSuchElementException)
67
+ end
68
+
69
+ it 'should return true for empty on none' do
70
+ expect(option(empty).empty?).to eq(true)
71
+ end
72
+
73
+ it 'should return false for defined on none' do
74
+ expect(option(empty).defined?).to eq(false)
75
+ end
76
+
77
+ it 'should return the else in get_or_else for none' do
78
+ expect(option(empty).get_or_else(1)).to eq(1)
79
+ end
80
+
81
+ it 'should return nil in get_or_nil for none' do
82
+ expect(option(empty).get_or_nil).to eq(nil)
83
+ end
84
+
85
+ it 'should raise exception in get_or_throw for none' do
86
+ expect { option(empty).get_or_throw(Exception, 'oops') }.to raise_error(Exception)
87
+ end
88
+
89
+ it 'should return sequence for none' do
90
+ expect(option(empty).to_seq).to eq(sequence(none))
91
+ end
92
+
93
+ it 'should return false for contains for none' do
94
+ expect(option(empty).contains('ooo')).to eq(false)
95
+ end
96
+
97
+ it 'should return false for exists for none' do
98
+ pending('needs work')
99
+ pass
100
+ end
101
+
102
+ it 'should return false for contains for none' do
103
+ expect(option(empty).contains('ooo')).to eq(false)
104
+ end
105
+
106
+ it 'should return original sequence for join on none' do
107
+ expect(option(empty).join(sequence(1,2))).to eq(sequence(1,2))
108
+ expect(option(empty).join([1,2])).to eq(sequence([1,2]))
109
+ end
110
+
111
+ it 'should know the type' do
112
+ expect(option(empty).is_none?).to be(true)
113
+ expect(option(empty).is_some?).to be(false)
114
+ expect(option(1).is_none?).to be(false)
115
+ expect(option(1).is_some?).to be(true)
116
+ end
117
+
118
+
48
119
 
49
120
  end
data/spec/pair_spec.rb CHANGED
@@ -1,5 +1,4 @@
1
- require 'rspec'
2
- require_relative '../lib/totally_lazy'
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
2
 
4
3
  describe 'Pair' do
5
4
 
@@ -37,4 +36,8 @@ describe 'Pair' do
37
36
  expect(pair('1', '2').to_f).to eq({1.0 => 2.0})
38
37
  end
39
38
 
39
+ it 'should support each' do
40
+ expect(pair(1,2).each{|x| x }).to eq([1,2])
41
+ end
42
+
40
43
  end