coroutines 0.1.1 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d94b2f0be52e246350f8eff0bbdef56df5bfd9ed
4
- data.tar.gz: d71f2c7e340d258ac7588d5e315ee1d8fecb3efa
3
+ metadata.gz: 6075ff02c811ae434bd45a95c49d4e55dbef28a7
4
+ data.tar.gz: 6533a5e7ab42de553f513c93fee0622fb081f755
5
5
  SHA512:
6
- metadata.gz: 4b073d2df415afdc01b406a71fce37a04a94218a5a2db425ac4bc20f5fa68dc06e121e6983020ebe6163f409af87dcc66fd2397971375f239985a7de3429b52d
7
- data.tar.gz: ded24fe0ab150e7989b55c1413093262abe211bea74d5a593eb9c10abd6a2dfa12c1b205a7cf03bc028162a3d13820e3bb98965036b95b93a257b89884e0cd27
6
+ metadata.gz: ff68457b130b88bebee3f5df84e3f6cd97b6e7fbef3b9fff7c0b58acf353052abcb4e0a2e976ab02613e7e14f1471ea01f6ab3f7f2a90550a08d62569c520981
7
+ data.tar.gz: 30b78a1aa708333c17b6d366255d286cdc65252f96deb1debb6748ce09345ed8d144fe9adf8d10f031897b0b4681f345f2f76b586d7f4901259942e7fc65a1e5
data/README.rdoc CHANGED
@@ -4,13 +4,23 @@ Producers are already provided by Ruby's built-in Enumerator class; this
4
4
  library provides Transformer and Consumer classes that work analogously. In
5
5
  particular, they are also based on Fiber and not on threads (as in some other
6
6
  producer/consumer libraries). Also provides a module Sink, which is analogous
7
- to Enumerable, and Enumerable/Transformer/Sink composition using overloaded
8
- <= and >= operators.
7
+ to Enumerable, and Enumerable/Transformer/Sink composition.
9
8
 
10
9
  == Installing
11
10
  gem install coroutines
12
11
 
13
12
  == Using
13
+ ---
14
+ <b>Major change in version 0.2.0:</b> The preferred way of connecting enumerables,
15
+ transformers and sinks is now to use Enumerable#out_connect, Sink#in_connect,
16
+ Transformer#out_connect, Transformer#in_connect. I currently tend to the
17
+ conclusion that descriptive names are more idiomatic Ruby (while the operator
18
+ notation is rather a bit Haskellish). The <= and >= operators are still
19
+ provided by default for backwards-compatibility, and for comparing the relative
20
+ benefits of both notations. However, please consider their usage without
21
+ explicitly requiring 'coroutines/operators' as deprecated. Feedback welcome.
22
+ ---
23
+
14
24
  A simple consumer:
15
25
 
16
26
  require 'coroutines'
@@ -44,11 +54,11 @@ A simple transformer:
44
54
  end
45
55
 
46
56
  tr = trans_for :running_sum, 3 # => #<Transformer: main:running_sum>
47
- sums = (1..10) >= tr # => #<Enumerator: #<Transformer: main:running_sum> <= 1..10>
57
+ sums = tr.in_connect(1..10) # => #<Enumerator::Lazy: #<Transformer: main:running_sum> <= 1..10>
48
58
  sums.to_a # => [4, 6, 9, 13, 18, 24, 31, 39, 48, 58]
49
59
 
50
60
  tr = trans_for :running_sum, 0 # => #<Transformer: main:running_sum>
51
- collect_sums = tr >= []
61
+ collect_sums = tr.out_connect([]) # => #<Consumer: #<Transformer: main:running_sum> >= []>
52
62
  collect_sums << 1 << 1 << 2 << 3 << 5
53
63
  collect_sums.close # => [1, 2, 4, 7, 12]
54
64
 
@@ -66,60 +76,99 @@ _accept_ sequences of values:
66
76
  [] << "a" << "b" << "c" # => ["a", "b", "c"]
67
77
  "" << "a" << "b" << "c" # => "abc"
68
78
 
69
- The +coroutines+ library provides the mixin Sink for such classes. Among
70
- other methods, this provides an operator <= for "connecting" sources (i.e.
71
- enumerables) to sinks.
79
+ The +coroutines+ library provides the mixin Sink for such classes. Among other
80
+ methods, this provides Sink#in_connect, which "connects" an enumerable (as a
81
+ "source" of values) to a sink's "input". Upon connecting, the enumerable is
82
+ iterated and each value is appended to the sink; then the sink is closed (by
83
+ calling its #close method). Sink#in_connect returns whatever #close returns,
84
+ which defaults to simply returning the sink itself (see Sink#close).
72
85
 
73
- open("test.txt", "w") <= ["a", "b", "c"] # write "abc" to test.txt
74
- ["a", "b", "c"] <= ("d".."f") # => ["a", "b", "c", "d", "e", "f"]
75
- "abc" <= ("d".."f") # => "abcdef"
86
+ open("test.txt", "w").in_connect(["a", "b", "c"]) # write "abc" to test.txt
87
+ ["a", "b", "c"].in_connect("d".."f") # => ["a", "b", "c", "d", "e", "f"]
88
+ "abc".in_connect("d".."f") # => "abcdef"
76
89
 
77
- Note that <= closes the sink after having exhausted the enumerable,
78
- returning whatever #close returns. close defaults to simply returning the
79
- sink itself, as in the Array and String examples above. For IO, which has
80
- its own close implementation, this implies that the file descriptor will be
81
- closed after the <= operation finishes. If this is not what you want, use
82
- dup:
90
+ Note that for IO/File objects, this implies that the file descriptor will be
91
+ closed after in_connect finishes. If this is not what you want, use dup:
83
92
 
84
- $stdout.dup <= ["a", "b", "c"] # print "abc"
93
+ $stdout.dup.in_connect(["a", "b", "c"]) # print "abc"
85
94
 
86
- For symmetry, the coroutines library augments Enumerable with an operator
87
- >= that mirrors <=:
95
+ For symmetry, the coroutines library also implements Enumerable#out_connect, which
96
+ mirrors Sink#in_connect:
88
97
 
89
- ("d".."f") >= "abc" # => "abcdef"
98
+ ("d".."f").out_connect("abc") # => "abcdef"
90
99
 
91
100
  == Pipelines involving transformers
92
- Re-using the running_sum example from above:
101
+ We'll be re-using the running_sum example from above.
93
102
 
94
- (1..10) >= trans_for(:running_sum, 0) >= proc{|x| x.to_s + "\n" } >= $stdout.dup
103
+ (1..10).
104
+ out_connect(trans_for :running_sum, 0).
105
+ lazy.map{|x| x.to_s + "\n"}.
106
+ out_connect($stdout.dup)
95
107
 
96
- What does this do? Well, it takes the sequences of integers from 1 to 10, then
97
- computes the running sum, then convers each partial sum to a string, and
108
+ What does this do? It takes the sequences of integers from 1 to 10, then
109
+ computes the running sum, then converts each partial sum to a string, and
98
110
  finally prints out each string to $stdout. Except that the "thens" in the
99
111
  previous sentence are not entirely correct, since the processing stages run in
100
- parallel (using coroutines, so blocking IO in one stage will block all other
101
- stages). Instead of (1..10), we could have a File and iterate over GBs of data,
102
- and at no point would we need to have the entire sequence in memory.
112
+ parallel (using coroutines where required, so blocking IO in one stage will
113
+ block all other stages). Instead of (1..10), we could have a File and iterate
114
+ over GBs of data, and at no point would we need to have the entire sequence in
115
+ memory.
116
+
117
+ In the above example, the "lazyness" of the pipeline - that is, the fact that
118
+ we don't have to keep the complete sequence of values in memory at any stage -
119
+ requires Enumerable#lazy. If we replace the lazy.map with a simple map, the
120
+ complete sequence of strings will be stored in an intermediate Array.
121
+
122
+ [ In order to avoid confusing readers it should be noted that lazy enumerators
123
+ were added in Ruby 2.0; the coroutines gem therefore depends on Charles Oliver
124
+ Nutter's lazy_enumerator gem, which contains a backport of the feature to
125
+ Ruby 1.8/1.9 ]
103
126
 
104
127
  Any part of a pipeline can be passed around and stored as an individual object:
105
128
 
106
- enum = (1..10) >= trans_for(:running_sum, 0) >= proc{|x| x.to_s + "\n" } # => an Enumerator
107
- cons = trans_for(:running_sum, 0) >= proc{|x| x.to_s + "\n" } >= $stdout.dup # => a Consumer
108
- trans = trans_for(:running_sum, 0) >= proc{|x| x.to_s + "\n" } # => a Transformer
129
+ (1..10).
130
+ out_connect(trans_for :running_sum, 0).
131
+ lazy.map{|x| x.to_s + "\n"}
132
+ # => an Enumerator
109
133
 
110
- == map/filter equivalent pipelines
111
- As shown above, a Proc object can be used in place of a Transformer. Connecting
112
- transformer procs to enums, sinks or other procs is special-cased to minimize
113
- overhead, so that something like
134
+ trans_for(:running_sum, 0).
135
+ lazy.map{|x| x.to_s + "\n"}.
136
+ out_connect($stdout.dup)
137
+ # => a Consumer
114
138
 
115
- (1..9) >= proc{|x| x.to_s if x.even? } >= ""
139
+ trans_for(:running_sum, 0).
140
+ lazy.map{|x| x.to_s + "\n"}
141
+ # => a Transformer
142
+
143
+ Note however that, while the last example works for map and some other common
144
+ Enumerable methods, not all of the Enumerable API is implemented yet.
116
145
 
117
- is just syntactic sugar for
146
+ == Connect operators
147
+ As explained above, the overloaded <= and >= operators are currently provided
148
+ by default; but using them without explicitly requiring 'coroutines/operators'
149
+ is deprecated.
118
150
 
119
- (1..9).lazy.select{|x| x.even? }.map{|x| x.to_s }.inject(""){|memo, x| memo << x }
151
+ 'coroutines/operators' provides a short-hand notation for in_connect,
152
+ out_connect and Enumerable#filter_map by overloading >= and <=:
120
153
 
121
- (And yes, the latter could also be written more succinctly if Enumerator::Lazy
122
- would provide operations like filter_map and join.)
154
+ open("test.txt", "w") <= ["a", "b", "c"] # write "abc" to test.txt
155
+ ["a", "b", "c"] <= ("d".."f") # => ["a", "b", "c", "d", "e", "f"]
156
+ "abc" <= ("d".."f") # => "abcdef"
157
+ enum = (1..10) >= trans_for(:running_sum, 0) >= proc{|x| x.to_s + "\n" } # => an Enumerator
158
+ cons = trans_for(:running_sum, 0) >= proc{|x| x.to_s + "\n" } >= $stdout.dup # => a Consumer
159
+ trans = trans_for(:running_sum, 0) >= proc{|x| x.to_s + "\n" } # => a Transformer
160
+
161
+ where a Proc object in a pipeline is interpreted as if it were an argument to Enumerable#filter_map; i.e. the following to are equivalent:
162
+
163
+ (1..9) >= proc{|x| x.to_s if x.even? } >= ""
164
+ (1..9).lazy.filter_map{|x| x.to_s if x.even? }.out_connect("")
165
+
166
+ Apart from saving a few keystrokes (d'oh...), this has a the advantage that all
167
+ elements of a pipeline are lazy _by default_. When using map, filter and
168
+ friends, forgetting to drop a "lazy" in the right place causes this part of the
169
+ pipeline to become strict (but of course it may still produce the intended
170
+ results!). This type of bug can be hard to catch - unless you're always
171
+ testing with production-sized data sets.
123
172
 
124
173
  == Links and Acknowledgements
125
174
  Inspired by {David M. Beazley's Generator Tricks}[http://www.dabeaz.com/generators] (Python)
@@ -139,7 +188,9 @@ Consumer and Transformer manually in order to use them; and you'll have to be
139
188
  more explicit when constructing certain pipelines.
140
189
 
141
190
  Patched core modules and classes are:
142
- Enumerable:: add Enumerable#>= operator
191
+ Enumerable:: add Enumerable#filter_map, Enumerable#out_connect and
192
+ Enumerable#>= operator
193
+ Enumerator::Lazy:: add Enumerator::Lazy#filter_map
143
194
  IO, Array, String:: include Sink mixin
144
195
  Hash:: define Hash#<< operator and include Sink mixin
145
196
  Method:: define Method#<< operator, define Method#close as an alias
@@ -148,3 +199,10 @@ Object:: define Object#await, Object#consum_for and Object#trans_for
148
199
  Proc:: define Proc#to_trans, Proc#<= and Proc#>=
149
200
  Symbol:: define Symbol#to_trans, Symbol#<= and Symbol#>=
150
201
 
202
+ == Contributing
203
+ 1. Fork it {on Github}[https://github.com/nome/coroutines]
204
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
205
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
206
+ 4. Push to the branch (`git push origin my-new-feature`)
207
+ 5. Create new Pull Request
208
+
@@ -1,5 +1,6 @@
1
1
  require 'fiber'
2
2
  require 'coroutines/sink'
3
+ require 'lazy_enumerator' unless defined? Enumerator::Lazy
3
4
 
4
5
  # A class implementing consumer coroutines
5
6
  #
@@ -7,7 +8,7 @@ require 'coroutines/sink'
7
8
  # * Object#consum_for
8
9
  # * Object#consumer
9
10
  # * Consumer.new
10
- # * Transformer#>=
11
+ # * Transformer#out_connect
11
12
  #
12
13
  # See Object#consum_for for an explanation of the basic concepts.
13
14
  class Consumer
@@ -111,13 +112,13 @@ end
111
112
  # Transformers are pieces of code that accept input values and produce output
112
113
  # values (without returning from their execution context like in a regular
113
114
  # method call). They are used by connecting either their input to an enumerable
114
- # or their output to a sink (or both, using Sink#<= or Enumerable#>=). An
115
- # enumerable is any object implementing an iterator method each; a sink is
116
- # any object implementing the << operator (which is assumed to store or
117
- # output the supplied values in some form).
115
+ # or their output to a sink (or both, using Sink#in_connect or
116
+ # Enumerable#out_connect). An enumerable is any object implementing an iterator
117
+ # method each; a sink is any object implementing the << operator (which is
118
+ # assumed to store or output the supplied values in some form).
118
119
  #
119
120
  # The input of a transformer is connected to an enumerable +enum+ using
120
- # trans <= enum, the result of which is a new Enumerator instance. The
121
+ # trans.in_connect(enum), the result of which is a new Enumerator instance. The
121
122
  # transformer is started upon iterating over this Enumerator. Whenever the
122
123
  # transformer requests a new input value (see Object#trans_for and
123
124
  # Transformer#new for how to do this), iteration over +enum+ is resumed. The
@@ -128,19 +129,20 @@ end
128
129
  # terminate without requesting any more values (though it may execute e.g. some
129
130
  # cleanup actions).
130
131
  #
131
- # The output of a transformer is connected to a sink using trans >= sink, the
132
- # result of which is a new Consumer instance. The transformer starts
133
- # executing right away; when it requests its first input value, the >=
134
- # operation returns. Input values supplied using << to the enclosing
135
- # Consumer are forwarded to the transformer by resuming execution at the
136
- # point where it last requested an input value. Output values produced by the
132
+ # The output of a transformer is connected to a sink using
133
+ # trans.out_connect(sink), the result of which is a new Consumer instance. The
134
+ # transformer starts executing right away; when it requests its first input
135
+ # value, out_connect returns. Input values supplied using << to the enclosing
136
+ # Consumer are forwarded to the transformer by resuming execution at the point
137
+ # where it last requested an input value. Output values produced by the
137
138
  # transformer are fed to sink#<<. After terminating, the result of the new
138
139
  # Consumer is the value returned by sink.close (see Consumer#result and
139
140
  # Consumer#close).
140
141
  #
141
142
  # Transformers can also be chained together by connecting the output of one the
142
- # input the next using trans >= other_trans or trans <= other_trans. See
143
- # Transformer#>= and Transformer#<= for details.
143
+ # input the next using trans.out_connect(other_trans) or
144
+ # trans.in_connect(other_trans). See Transformer#out_connect and
145
+ # Transformer#in_connect for details.
144
146
  #
145
147
  class Transformer
146
148
  # :call-seq:
@@ -158,7 +160,7 @@ class Transformer
158
160
  # loop { result += y.await; y.yield result }
159
161
  # end
160
162
  #
161
- # (1..3) >= running_sum >= [] # => [1, 3, 6]
163
+ # (1..3).out_connect(running_sum).out_connect([]) # => [1, 3, 6]
162
164
  #
163
165
  def initialize(&block)
164
166
  @self = block
@@ -177,15 +179,15 @@ class Transformer
177
179
  end
178
180
 
179
181
  # :call-seq:
180
- # trans <= other_trans -> new_trans
181
- # trans <= enum -> new_enum
182
+ # trans.in_connect(other_trans) -> new_trans
183
+ # trans.in_connect(enum) -> lazy_enumerator
182
184
  #
183
185
  # In the first form, creates a new Transformer that has the input of
184
186
  # +trans+ connected to the output of +other_trans+.
185
187
  #
186
- # In the second form, creates a new Enumerator by connecting +enum+ to
187
- # the input of +trans+. See Transformer for details.
188
- def <=(source)
188
+ # In the second form, creates a new lazy Enumerator by connecting the
189
+ # output of +enum+ to the input of +trans+. See Transformer for details.
190
+ def in_connect(source)
189
191
  if not source.respond_to? :each
190
192
  return source.to_trans.transformer_chain self
191
193
  end
@@ -196,9 +198,9 @@ class Transformer
196
198
  source_enum.next
197
199
  end
198
200
  @self.call(y)
199
- end
201
+ end.lazy
200
202
 
201
- description = "#<Enumerator: #{inspect} <= #{source.inspect}>"
203
+ description = "#<Enumerator::Lazy: #{inspect} <= #{source.inspect}>"
202
204
  enum.define_singleton_method :inspect do
203
205
  description
204
206
  end
@@ -207,15 +209,15 @@ class Transformer
207
209
  end
208
210
 
209
211
  # :call-seq:
210
- # trans >= other_trans -> new_trans
211
- # trans >= sink -> consum
212
+ # trans.out_connect(other_trans) -> new_trans
213
+ # trans.out_connect(sink) -> consum
212
214
  #
213
215
  # In the first form, creates a new Transformer that has the output of
214
216
  # +trans+ connected to the input of +other_trans+.
215
217
  #
216
218
  # In the second form, creates a new Consumer by connecting the output of
217
- # +trans+ to +sink+. See Transformer for details.
218
- def >=(sink)
219
+ # +trans+ to the input of +sink+. See Transformer for details.
220
+ def out_connect(sink)
219
221
  if not sink.respond_to? :<<
220
222
  return transformer_chain sink.to_trans
221
223
  end
@@ -223,6 +225,7 @@ class Transformer
223
225
  consum = Consumer.new do |y|
224
226
  y.define_singleton_method :yield do |args|
225
227
  sink << args
228
+ y
226
229
  end
227
230
  y.singleton_class.instance_eval { alias_method :<<, :yield }
228
231
  begin
@@ -240,6 +243,299 @@ class Transformer
240
243
  consum
241
244
  end
242
245
 
246
+ class Lazy
247
+ def initialize(trans)
248
+ @trans = trans.instance_variable_get :@self
249
+ end
250
+
251
+ def lazy
252
+ self
253
+ end
254
+
255
+ class Yielder
256
+ def initialize(wrapped)
257
+ @wrapped = wrapped
258
+ end
259
+ def await
260
+ @wrapped.await
261
+ end
262
+
263
+ def define_yield(&block)
264
+ singleton_class.instance_eval do
265
+ define_method(:yield, &block)
266
+ alias_method :<<, :yield
267
+ end
268
+ end
269
+ end
270
+
271
+ def count
272
+ Consumer.new do |y|
273
+ yy = Yielder.new y
274
+ n = 0
275
+ yy.define_yield do |*values|
276
+ n += 1
277
+ yy
278
+ end
279
+ @trans.call yy
280
+ n
281
+ end
282
+ end
283
+
284
+ def drop(n)
285
+ Transformer.new do |y|
286
+ yy = Yielder.new y
287
+ to_drop = n
288
+ yy.define_yield do |*values|
289
+ if to_drop > 0
290
+ to_drop -= 1
291
+ else
292
+ y.yield(*values)
293
+ end
294
+ yy
295
+ end
296
+ @trans.call yy
297
+ end
298
+ end
299
+
300
+ def drop_while(&block)
301
+ Transformer.new do |y|
302
+ yy = Yielder.new y
303
+ dropping = true
304
+ yy.define_yield do |*values|
305
+ if dropping
306
+ if not block.call(*values)
307
+ dropping = false
308
+ y.yield(*values)
309
+ end
310
+ else
311
+ y.yield(*values)
312
+ end
313
+ yy
314
+ end
315
+ @trans.call yy
316
+ end
317
+ end
318
+
319
+ def each(&block)
320
+ Consumer.new do |y|
321
+ yy = Yielder.new y
322
+ yy.define_yield do |*values|
323
+ block.call(*values)
324
+ yy
325
+ end
326
+ @trans.call yy
327
+ end
328
+ end
329
+
330
+ def filter_map(&block)
331
+ Transformer.new do |y|
332
+ yy = Yielder.new y
333
+ yy.define_yield do |*values|
334
+ x = block.call(*values)
335
+ y.yield(x) unless x.nil?
336
+ yy
337
+ end
338
+ @trans.call yy
339
+ end
340
+ end
341
+
342
+ def flat_map(&block)
343
+ Transformer.new do |y|
344
+ yy = Yielder.new y
345
+ yy.define_yield do |*values|
346
+ x = block.call(*values)
347
+ if x.respond_to? :to_ary
348
+ x.to_ary.each{|xx| y.yield xx }
349
+ else
350
+ y.yield x
351
+ end
352
+ yy
353
+ end
354
+ @trans.call yy
355
+ end
356
+ end
357
+ alias_method :collect_concat, :flat_map
358
+
359
+ def map(&block)
360
+ Transformer.new do |y|
361
+ yy = Yielder.new y
362
+ yy.define_yield do |*values|
363
+ y.yield(block.call(*values))
364
+ yy
365
+ end
366
+ @trans.call yy
367
+ end
368
+ end
369
+ alias_method :collect, :map
370
+
371
+ def out_connect(other)
372
+ Transformer.new(&@trans).out_connect(other)
373
+ end
374
+
375
+ def reduce(*args)
376
+ if not block_given?
377
+ if args.size == 1
378
+ return reduce(&args[0].to_proc)
379
+ elsif args.size == 2
380
+ return reduce(args[0], &args[1].to_proc)
381
+ else
382
+ raise ArgumentError, "wrong number of arguments"
383
+ end
384
+ end
385
+ raise ArgumentError, "wrong number of arguments" if args.size > 1
386
+ block = proc
387
+
388
+ memo = if args.empty? then nil else args[0] end
389
+ Consumer.new do |y|
390
+ yy = Yielder.new y
391
+ yy.define_yield do |value|
392
+ if memo.nil?
393
+ memo = value
394
+ else
395
+ memo = block.call memo, value
396
+ end
397
+ yy
398
+ end
399
+ @trans.call yy
400
+ memo
401
+ end
402
+ end
403
+ alias_method :inject, :reduce
404
+
405
+ def reject(&block)
406
+ Transformer.new do |y|
407
+ yy = Yielder.new y
408
+ yy.define_yield do |*values|
409
+ y.yield(*values) unless block.call(*values)
410
+ yy
411
+ end
412
+ @trans.call yy
413
+ end
414
+ end
415
+
416
+ def select(&block)
417
+ Transformer.new do |y|
418
+ yy = Yielder.new y
419
+ yy.define_yield do |*values|
420
+ y.yield(*values) if block.call(*values)
421
+ yy
422
+ end
423
+ @trans.call yy
424
+ end
425
+ end
426
+
427
+ def take(n)
428
+ Transformer.new do |y|
429
+ yy = Yielder.new y
430
+ to_take = n
431
+ yy.define_yield do |*values|
432
+ if to_take > 0
433
+ y.yield(*values)
434
+ to_take -= 1
435
+ else
436
+ raise StopIteration
437
+ end
438
+ yy
439
+ end
440
+ @trans.call yy
441
+ end
442
+ end
443
+
444
+ def take_while(&block)
445
+ Transformer.new do |y|
446
+ yy = Yielder.new y
447
+ yy.define_yield do |*values|
448
+ if block.call(*values)
449
+ y.yield(*values)
450
+ else
451
+ raise StopIteration
452
+ end
453
+ yy
454
+ end
455
+ @trans.call yy
456
+ end
457
+ end
458
+
459
+ def sort
460
+ Consumer.new do |y|
461
+ yy = Yielder.new y
462
+ result = []
463
+ yy.define_yield do |value|
464
+ result << value
465
+ yy
466
+ end
467
+ @trans.call yy
468
+ result.sort
469
+ end
470
+ end
471
+
472
+ def sort_by(&block)
473
+ Consumer.new do |y|
474
+ yy = Yielder.new y
475
+ result = []
476
+ yy.define_yield do |value|
477
+ result << value
478
+ yy
479
+ end
480
+ @trans.call yy
481
+ result.sort_by(&block)
482
+ end
483
+ end
484
+
485
+ def to_a
486
+ Consumer.new do |y|
487
+ yy = Yielder.new y
488
+ result = []
489
+ yy.define_yield do |value|
490
+ result << value
491
+ yy
492
+ end
493
+ @trans.call yy
494
+ result
495
+ end
496
+ end
497
+
498
+ def to_h
499
+ Consumer.new do |y|
500
+ yy = Yielder.new y
501
+ result = {}
502
+ yy.define_yield do |value|
503
+ result[value[0]] = value[1]
504
+ yy
505
+ end
506
+ @trans.call yy
507
+ result
508
+ end
509
+ end
510
+ end
511
+
512
+ # :call-seq:
513
+ # trans.lazy -> lazy_trans
514
+ #
515
+ # Returns a "lazy enumeration like" transformer. More precisely, the object
516
+ # returned can in many situations be used as if it were an Enumerator
517
+ # returned by trans.in_connect, since it implements work-alikes of many
518
+ # Enumerable methods. Note however that the return types of those methods
519
+ # differ: where an Enumerator method would return a new Enumerator, the
520
+ # corresponding lazy transformer returns a new Transformer; where an
521
+ # Enumerator would return a single value, the lazy transformer returns a
522
+ # Consumer.
523
+ #
524
+ # Example:
525
+ #
526
+ # running_sum = Transformer.new do |y|
527
+ # result = 0
528
+ # loop { result += y.await; y.yield result }
529
+ # end
530
+ #
531
+ # sum_str = running_sum.lazy.map{|x| x.to_s}
532
+ # # => a Transformer
533
+ # (1..10).out_connect(sum_str).to_a
534
+ # # => ["1", "3", "6", "10", "15", "21", "28", "36", "45", "55"]
535
+ def lazy
536
+ Lazy.new self
537
+ end
538
+
243
539
  protected
244
540
 
245
541
  class FirstYielder < Consumer::Yielder
@@ -249,6 +545,7 @@ class Transformer
249
545
  end
250
546
  def yield(*args)
251
547
  Fiber.yield(:yield, *args)
548
+ self
252
549
  end
253
550
  alias_method :<<, :yield
254
551
  else
@@ -260,6 +557,7 @@ class Transformer
260
557
  def yield(*args)
261
558
  item = Fiber.yield(:yield, *args)
262
559
  raise(*item.args) if item.instance_of? @@exception_wrapper
560
+ self
263
561
  end
264
562
  alias_method :<<, :yield
265
563
  end
@@ -305,6 +603,7 @@ class Transformer
305
603
 
306
604
  def yield(*args)
307
605
  @y.yield(*args)
606
+ self
308
607
  end
309
608
  alias_method :<<, :yield
310
609
  end
@@ -0,0 +1,180 @@
1
+ require 'coroutines/base'
2
+
3
+ module Enumerable
4
+ # :call-seq:
5
+ # enum >= sink -> obj
6
+ # enum >= trans -> new_enum
7
+ #
8
+ # In the first form, iterate over +enum+ and write each result to +sink+
9
+ # using <<; then return the result of sink.close.
10
+ #
11
+ # In the second form, create a new Enumerator by connecting the output of
12
+ # +enum+ to the input of +trans+ (which must be convertible to a
13
+ # Transformer using the to_trans method).
14
+ def >=(other)
15
+ if other.respond_to? :<<
16
+ begin
17
+ each { |x| other << x }
18
+ rescue StopIteration
19
+ end
20
+ other.close
21
+ elsif other.respond_to? :to_trans
22
+ other <= self
23
+ end
24
+ end
25
+ end
26
+
27
+ module Sink
28
+ # :call-seq:
29
+ # sink <= enum -> obj
30
+ # sink <= trans -> new_consum
31
+ #
32
+ # In the first form, iterate over +enum+ and write each result to +sink+
33
+ # using <<; then return the result of sink.close.
34
+ #
35
+ # In the second form, create a new Consumer by connecting the output of
36
+ # +trans+ (which must be convertible to a Transformer using the
37
+ # to_trans method) to the input of +sink+.
38
+ def <=(other)
39
+ if other.respond_to? :each
40
+ begin
41
+ other.each { |x| self << x }
42
+ rescue StopIteration
43
+ end
44
+ close
45
+ elsif other.respond_to? :to_trans
46
+ other >= self
47
+ end
48
+ end
49
+ end
50
+
51
+ class Transformer
52
+ alias_method :<=, :in_connect
53
+ alias_method :>=, :out_connect
54
+ end
55
+
56
+ class Symbol
57
+ # :call-seq:
58
+ # sym.to_trans -> transformer
59
+ #
60
+ # Allows implicit conversion of Symbol to Transformer. The transformer
61
+ # accepts any objects as input, calls the method given by +sym+ on each and
62
+ # outputs all non-nil results of the method. See Proc#to_trans for details.
63
+ #
64
+ # example:
65
+ #
66
+ # collector = [] <= :to_s
67
+ # collector << 1 << 4 << 9
68
+ # collector.close # => ["1", "4", "9"]
69
+ #
70
+ def to_trans
71
+ Transformer.new do |y|
72
+ loop do
73
+ value = y.await.send self
74
+ y.yield value unless value.nil?
75
+ end
76
+ end
77
+ end
78
+
79
+ # :call-seq:
80
+ # sym <= trans -> new_trans
81
+ # sym <= enum -> new_enum
82
+ #
83
+ # Equivalent to sym.to_trans <= trans/enum, except that it uses a more
84
+ # efficient implementation.
85
+ def <=(source)
86
+ to_proc <= source
87
+ end
88
+
89
+ # :call-seq:
90
+ # sym >= trans -> new_trans
91
+ # sym >= sink -> new_consumer
92
+ #
93
+ # Equivalent to sym.to_trans >= trans/sink, except that it uses a more
94
+ # efficient implementation.
95
+ def >=(sink)
96
+ to_proc >= sink
97
+ end
98
+ end
99
+
100
+ class Proc
101
+ # :call-seq:
102
+ # proc.to_trans -> transformer
103
+ #
104
+ # Allows implicit conversion of Proc to Transformer. The transformer is a
105
+ # combination of map and filter over its input values: For each input
106
+ # value, +proc+ is called with the input value as parameter. Every non-nil
107
+ # value returned by +proc+ is yielded as an output value.
108
+ #
109
+ # This is similar to Enumerable#map followed by Array#compact, but without
110
+ # constructing the intermediate Array (so it's even more similar to
111
+ # something like enum.lazy.map(&proc).reject(&:nil?), using the lazy
112
+ # enumerator introduced in Ruby 2.0).
113
+ #
114
+ # Example:
115
+ #
116
+ # (1..10) >= proc{|x| x.to_s + ", " if x.even? } >= ""
117
+ # # => "2, 4, 6, 8, 10, "
118
+ #
119
+ def to_trans
120
+ Transformer.new do |y|
121
+ loop do
122
+ value = self.call(y.await)
123
+ y.yield value unless value.nil?
124
+ end
125
+ end
126
+ end
127
+
128
+ # :call-seq:
129
+ # proc <= trans -> new_trans
130
+ # proc <= enum -> new_enum
131
+ #
132
+ # Equivalent to proc.to_trans <= trans/enum, except that it uses a more
133
+ # efficient implementation.
134
+ def <=(source)
135
+ if source.respond_to? :each
136
+ Enumerator.new do |y|
137
+ source.each do |obj|
138
+ value = call obj
139
+ y << value unless value.nil?
140
+ end
141
+ end.lazy
142
+ elsif source.respond_to? :to_proc
143
+ sp = source.to_proc
144
+ proc do |*args|
145
+ value = sp.call(*args)
146
+ if value.nil? then nil else self.call value end
147
+ end
148
+ elsif source.respond_to? :to_trans
149
+ # FIXME: this could be implemented more efficiently; proc doesn't
150
+ # need to run in a separate Fiber
151
+ self.to_trans <= source.to_trans
152
+ else
153
+ raise ArgumentError, "#{source.inspect} is neither an enumerable nor a transformer"
154
+ end
155
+ end
156
+
157
+ # :call-seq:
158
+ # proc >= trans -> new_trans
159
+ # proc >= sink -> new_consumer
160
+ #
161
+ # Equivalent to proc.to_trans >= trans/sink, except that it uses a more
162
+ # efficient implementation.
163
+ def >=(sink)
164
+ if sink.respond_to? :input_map
165
+ sink.input_reject(&:nil?).input_map(&self)
166
+ elsif sink.respond_to? :to_proc
167
+ sp = sink.to_proc
168
+ proc do |*args|
169
+ value = self.call(*args)
170
+ if value.nil? then nil else sp.call value end
171
+ end
172
+ elsif sink.respond_to? :to_trans
173
+ # FIXME: this could be implemented more efficiently; proc doesn't
174
+ # need to run in a separate Fiber
175
+ self.to_trans >= sink.to_trans
176
+ else
177
+ raise ArgumentError, "#{sink.inspect} is neither a sink nor a transformer"
178
+ end
179
+ end
180
+ end
@@ -11,8 +11,8 @@ module Sink
11
11
  end
12
12
 
13
13
  # :call-seq:
14
- # sink <= enum -> obj
15
- # sink <= trans -> new_consum
14
+ # sink.in_connect(enum) -> obj
15
+ # sink.in_connect(trans) -> new_consum
16
16
  #
17
17
  # In the first form, iterate over +enum+ and write each result to +sink+
18
18
  # using <<; then return the result of sink.close.
@@ -20,7 +20,7 @@ module Sink
20
20
  # In the second form, create a new Consumer by connecting the output of
21
21
  # +trans+ (which must be convertible to a Transformer using the
22
22
  # to_trans method) to +sink+.
23
- def <=(other)
23
+ def in_connect(other)
24
24
  if other.respond_to? :each
25
25
  begin
26
26
  other.each { |x| self << x }
@@ -28,7 +28,7 @@ module Sink
28
28
  end
29
29
  close
30
30
  elsif other.respond_to? :to_trans
31
- other >= self
31
+ other.to_trans.out_connect(self)
32
32
  end
33
33
  end
34
34
 
data/lib/coroutines.rb CHANGED
@@ -1,21 +1,22 @@
1
1
  require 'coroutines/base'
2
+ require 'coroutines/operators' # deprecated
2
3
 
3
4
  #--
4
- # Most of the Sink methods mirror Enumerable, except for <=
5
- # for symmetry, also add a >= method to Enumerable
5
+ # Most of the Sink methods mirror Enumerable, except for #in_connect
6
+ # for symmetry, also add an #out_connect method to Enumerable
6
7
  #++
7
8
  module Enumerable
8
9
  # :call-seq:
9
- # enum >= sink -> obj
10
- # enum >= trans -> new_enum
10
+ # enum.out_connect(sink) -> obj
11
+ # enum.out_connect(trans) -> new_enum
11
12
  #
12
13
  # In the first form, iterate over +enum+ and write each result to +sink+
13
14
  # using <<; then return the result of sink.close.
14
15
  #
15
- # In the second form, create a new Enumerator by connecting +enum+ to the
16
- # input of +trans+ (which must be convertible to a Transformer using the
17
- # to_trans method).
18
- def >=(other)
16
+ # In the second form, create a new Enumerator by connecting the output of
17
+ # +enum+ to the input of +trans+ (which must be convertible to a
18
+ # Transformer using the to_trans method).
19
+ def out_connect(other)
19
20
  if other.respond_to? :<<
20
21
  begin
21
22
  each { |x| other << x }
@@ -23,7 +24,34 @@ module Enumerable
23
24
  end
24
25
  other.close
25
26
  elsif other.respond_to? :to_trans
26
- other <= self
27
+ other.to_trans.in_connect(self)
28
+ end
29
+ end
30
+
31
+ # :call-seq:
32
+ # enum.filter_map {|obj| block } -> array
33
+ #
34
+ # For each +obj+ in in +enum+, calls +block+, and collects its non-nil
35
+ # return values into a new +array+.
36
+ #--
37
+ # taken from the API doc of Enumerator::Lazy.new
38
+ def filter_map(&block)
39
+ map(&block).compact
40
+ end
41
+ end
42
+
43
+ class Enumerator::Lazy
44
+ # :call-seq:
45
+ # enum.filter_map {|obj| block } -> an_enumerator
46
+ #
47
+ # Returns a new lazy Enumerator which iterates over all non-nil values
48
+ # returned by +block+ while +obj+ iterates over +enum+.
49
+ #--
50
+ # taken from the API doc of Enumerator::Lazy.new
51
+ def filter_map
52
+ Lazy.new(self) do |yielder, *values|
53
+ result = yield *values
54
+ yielder << result if result
27
55
  end
28
56
  end
29
57
  end
@@ -145,10 +173,10 @@ class Object
145
173
  # analogous to using Kernel#enum_for to create an Enumerator instance, or
146
174
  # using Object#consum_for to create a Consumer instance. The method is not
147
175
  # executed immediately. The resulting Transformer can be connected to an
148
- # enumerable (using transformer <= enum) or to a sink (using
149
- # transformer >= sink). The point at which the transformer method gets
150
- # started depends on how it is connected; in any case however, the method
151
- # will be called with the +args+ given to trans_for. See Transformer
176
+ # enumerable (using transformer.in_connect(enum)) or to a sink (using
177
+ # transformer.out_connect(sink)). The point at which the transformer method
178
+ # gets started depends on how it is connected; in any case however, the
179
+ # method will be called with the +args+ given to trans_for. See Transformer
152
180
  # for details.
153
181
  #
154
182
  # Within the transformer method, #await can be used to read the next
@@ -164,7 +192,7 @@ class Object
164
192
  # end
165
193
  #
166
194
  # tr = trans_for :running_sum, 3 #=> #<Transformer: main:running_sum>
167
- # sums = (1..10) >= tr #=> #<Enumerator: #<Transformer: main:running_sum> <= 1..10>
195
+ # sums = (1..10).out_connect(tr) #=> #<Enumerator: #<Transformer: main:running_sum> <= 1..10>
168
196
  # sums.to_a #=> [4, 6, 9, 13, 18, 24, 31, 39, 48, 58]
169
197
  #
170
198
  def trans_for(meth, *args)
@@ -180,155 +208,3 @@ class Object
180
208
  end
181
209
  end
182
210
 
183
- class Symbol
184
- # :call-seq:
185
- # sym.to_trans -> transformer
186
- #
187
- # Allows implicit conversion of Symbol to Transformer. The transformer
188
- # accepts any objects as input, calls the method given by +sym+ on each and
189
- # outputs all non-nil results of the method. See Proc#to_trans for details.
190
- #
191
- # example:
192
- #
193
- # collector = [] <= :to_s
194
- # collector << 1 << 4 << 9
195
- # collector.close # => ["1", "4", "9"]
196
- #
197
- def to_trans
198
- Transformer.new do |y|
199
- loop do
200
- value = y.await.send self
201
- y.yield value unless value.nil?
202
- end
203
- end
204
- end
205
-
206
- # :call-seq:
207
- # sym <= trans -> new_trans
208
- # sym <= enum -> new_enum
209
- #
210
- # Equivalent to sym.to_trans <= trans/enum, except that it uses a more
211
- # efficient implementation.
212
- def <=(source)
213
- to_proc <= source
214
- end
215
-
216
- # :call-seq:
217
- # sym >= trans -> new_trans
218
- # sym >= sink -> new_consumer
219
- #
220
- # Equivalent to sym.to_trans >= trans/sink, except that it uses a more
221
- # efficient implementation.
222
- def >=(sink)
223
- to_proc >= sink
224
- end
225
- end
226
-
227
- # Define a poor man's Enumerator::Lazy for Ruby < 2.0
228
- if defined? Enumerator::Lazy
229
- LazyEnumerator = Enumerator::Lazy
230
- else
231
- class LazyEnumerator
232
- def initialize(obj, &block)
233
- @obj = obj; @block = block
234
- end
235
-
236
- class Yielder
237
- def initialize(iter_block)
238
- @iter_block = iter_block
239
- end
240
- def yield(*values)
241
- @iter_block.call(*values)
242
- end
243
- alias_method :<<, :yield
244
- end
245
-
246
- def each(&iter_block)
247
- yielder = Yielder.new(iter_block)
248
- @obj.each do |*args|
249
- @block.call(yielder, *args)
250
- end
251
- end
252
- include Enumerable
253
- end
254
- end
255
-
256
- class Proc
257
- # :call-seq:
258
- # proc.to_trans -> transformer
259
- #
260
- # Allows implicit conversion of Proc to Transformer. The transformer is a
261
- # combination of map and filter over its input values: For each input
262
- # value, +proc+ is called with the input value as parameter. Every non-nil
263
- # value returned by +proc+ is yielded as an output value.
264
- #
265
- # This is similar to Enumerable#map followed by Array#compact, but without
266
- # constructing the intermediate Array (so it's even more similar to
267
- # something like enum.lazy.map(&proc).reject(&:nil?), using the lazy
268
- # enumerator introduced in Ruby 2.0).
269
- #
270
- # Example:
271
- #
272
- # (1..10) >= proc{|x| x.to_s + ", " if x.even? } >= ""
273
- # # => "2, 4, 6, 8, 10, "
274
- #
275
- def to_trans
276
- Transformer.new do |y|
277
- loop do
278
- value = self.call(y.await)
279
- y.yield value unless value.nil?
280
- end
281
- end
282
- end
283
-
284
- # :call-seq:
285
- # proc <= trans -> new_trans
286
- # proc <= enum -> new_enum
287
- #
288
- # Equivalent to proc.to_trans <= trans/enum, except that it uses a more
289
- # efficient implementation.
290
- def <=(source)
291
- if source.respond_to? :each
292
- LazyEnumerator.new(source) do |y, *args|
293
- value = call(*args)
294
- y << value unless value.nil?
295
- end
296
- elsif source.respond_to? :to_proc
297
- sp = source.to_proc
298
- proc do |*args|
299
- value = sp.call(*args)
300
- if value.nil? then nil else self.call value end
301
- end
302
- elsif source.respond_to? :to_trans
303
- # FIXME: this could be implemented more efficiently; proc doesn't
304
- # need to run in a separate Fiber
305
- self.to_trans <= source.to_trans
306
- else
307
- raise ArgumentError, "#{source.inspect} is neither an enumerable nor a transformer"
308
- end
309
- end
310
-
311
- # :call-seq:
312
- # proc >= trans -> new_trans
313
- # proc >= sink -> new_consumer
314
- #
315
- # Equivalent to proc.to_trans >= trans/sink, except that it uses a more
316
- # efficient implementation.
317
- def >=(sink)
318
- if sink.respond_to? :input_map
319
- sink.input_reject(&:nil?).input_map(&self)
320
- elsif sink.respond_to? :to_proc
321
- sp = sink.to_proc
322
- proc do |*args|
323
- value = self.call(*args)
324
- if value.nil? then nil else sp.call value end
325
- end
326
- elsif sink.respond_to? :to_trans
327
- # FIXME: this could be implemented more efficiently; proc doesn't
328
- # need to run in a separate Fiber
329
- self.to_trans >= sink.to_trans
330
- else
331
- raise ArgumentError, "#{sink.inspect} is neither a sink nor a transformer"
332
- end
333
- end
334
- end
data/tests/suite.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'test_sink'
2
+ require 'test_enumerable'
3
+ require 'test_coroutines'
4
+ require 'test_operators'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coroutines
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Knut Franke
@@ -9,15 +9,28 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2014-10-12 00:00:00.000000000 Z
12
- dependencies: []
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lazy_enumerator
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: |
14
28
  A library for creating and composing producer/transformer/consumer coroutines.
15
29
  Producers are already provided by Ruby's built-in Enumerator class; this
16
30
  library provides Transformer and Consumer classes that work analogously. In
17
31
  particular, they are also based on Fiber and not on threads (as in some other
18
32
  producer/consumer libraries). Also provides a module Sink, which is analogous
19
- to Enumerable, and Enumerable/Transformer/Sink composition using overloaded
20
- <= and >= operators.
33
+ to Enumerable, and Enumerable/Transformer/Sink composition.
21
34
  email: knut.franke@gmx.de
22
35
  executables: []
23
36
  extensions: []
@@ -27,8 +40,9 @@ files:
27
40
  - README.rdoc
28
41
  - lib/coroutines.rb
29
42
  - lib/coroutines/base.rb
43
+ - lib/coroutines/operators.rb
30
44
  - lib/coroutines/sink.rb
31
- - tests/test_coroutines.rb
45
+ - tests/suite.rb
32
46
  homepage: https://github.com/nome/coroutines
33
47
  licenses:
34
48
  - Ruby
@@ -55,5 +69,5 @@ signing_key:
55
69
  specification_version: 4
56
70
  summary: Library for producer/transformer/consumer coroutines
57
71
  test_files:
58
- - tests/test_coroutines.rb
59
- has_rdoc: false
72
+ - tests/suite.rb
73
+ has_rdoc: true
@@ -1,154 +0,0 @@
1
- require 'coroutines'
2
- require 'test/unit'
3
-
4
- class TestCoroutines < Test::Unit::TestCase
5
- def test_sink
6
- assert_equal("abcdef", "abc" <= ("d".."f"))
7
- assert_equal([1,2,3,4], [1] <= [2,3,4])
8
- end
9
-
10
- def test_source
11
- assert_equal("abcdef", ("d".."f") >= "abc")
12
- assert_equal([1,2,3,4], [2,3,4] >= [1])
13
- end
14
-
15
- def test_consumer
16
- c = Consumer.new { |y| [ y.await, y.await ] }
17
- c << :first << :second
18
- assert_equal([:first, :second], c.close)
19
- end
20
-
21
- def counter(start)
22
- result = start
23
- loop { result += await }
24
- "Final value: #{result}"
25
- end
26
- def test_consumer_method
27
- c = consum_for :counter, 10
28
- c << 10 << 1000 << 10000
29
- assert_equal("Final value: 11020", c.close)
30
- end
31
-
32
- def test_transformer
33
- rs = Transformer.new do |y|
34
- result = 0
35
- loop { result += y.await; y.yield result }
36
- end
37
- assert_equal([1, 3, 6], (1..3) >= rs >= [])
38
-
39
- rs = Transformer.new do |y|
40
- result = 0
41
- loop { result += y.await; y.yield result }
42
- end
43
- assert_equal([1, 3, 6], [] <= rs <= (1..3))
44
- end
45
-
46
- def test_transformer_chaining
47
- t1 = Transformer.new{|y| y.yield (y.await + "a")}
48
- t2 = Transformer.new{|y| y.yield (y.await + "b")}
49
- t = t1 >= t2
50
- result = %w{x y z} >= t >= []
51
- assert_equal(["xab"], result)
52
- end
53
-
54
- def test_associativity
55
- s = (1..3)
56
- def t1
57
- Transformer.new{|y| loop { y.yield y.await.to_s } }
58
- end
59
- def t2
60
- Transformer.new{|y| loop { y.yield (y.await + ",") } }
61
- end
62
-
63
- assert_equal("1,2,3,", s >= t1 >= t2 >= "")
64
- assert_equal("1,2,3,", s >= t1 >= (t2 >= ""))
65
- assert_equal("1,2,3,", s >= (t1 >= t2 >= ""))
66
- assert_equal("1,2,3,", s >= (t1 >= t2) >= "")
67
- assert_equal("1,2,3,", s >= ((t1 >= t2) >= ""))
68
-
69
- assert_equal("1,2,3,", "" <= t2 <= t1 <= s)
70
- assert_equal("1,2,3,", "" <= t2 <= (t1 <= s))
71
- assert_equal("1,2,3,", "" <= (t2 <= t1 <= s))
72
- assert_equal("1,2,3,", "" <= (t2 <= t1) <= s)
73
- assert_equal("1,2,3,", "" <= ((t2 <= t1) <= s))
74
- end
75
-
76
- def running_sum(start)
77
- result = start
78
- loop { result += await; yield result }
79
- end
80
- def test_transformer_method
81
- assert_equal([4, 6, 9], (1..3) >= trans_for(:running_sum, 3) >= [])
82
- assert_equal([4, 6, 9], (1..3) >= (trans_for(:running_sum, 3) >= []))
83
- end
84
-
85
- def test_stop_iteration
86
- consume_three = Consumer.new do |y|
87
- [y.await, y.await, y.await]
88
- end
89
- assert_equal([1,2,3], (1..Float::INFINITY) >= consume_three)
90
-
91
- limit_three = Transformer.new do |y|
92
- 3.times { y.yield y.await }
93
- end
94
- assert_equal([1,2,3], (1..Float::INFINITY) >= limit_three >= [])
95
- end
96
-
97
- def test_transformer_procs
98
- s = (1..3)
99
- t1_proc = proc{|x| x.to_s }
100
- t2_proc = proc{|x| x + "," }
101
- def t1
102
- Transformer.new{|y| loop { y.yield y.await.to_s } }
103
- end
104
- def t2
105
- Transformer.new{|y| loop { y.yield (y.await + ",") } }
106
- end
107
-
108
- assert_equal("1,2,3,", s >= t1_proc >= t2_proc >= "")
109
- assert_equal("1,2,3,", s >= t1_proc >= (t2_proc >= ""))
110
- assert_equal("1,2,3,", s >= (t1_proc >= t2_proc >= ""))
111
- assert_equal("1,2,3,", s >= (t1_proc >= t2_proc) >= "")
112
- assert_equal("1,2,3,", s >= ((t1_proc >= t2_proc) >= ""))
113
-
114
- assert_equal("1,2,3,", "" <= t2_proc <= t1_proc <= s)
115
- assert_equal("1,2,3,", "" <= t2_proc <= (t1_proc <= s))
116
- assert_equal("1,2,3,", "" <= (t2_proc <= t1_proc <= s))
117
- assert_equal("1,2,3,", "" <= (t2_proc <= t1_proc) <= s)
118
- assert_equal("1,2,3,", "" <= ((t2_proc <= t1_proc) <= s))
119
-
120
- assert_equal("1,2,3,", s >= t1_proc >= t2 >= "")
121
- assert_equal("1,2,3,", s >= t1_proc >= (t2 >= ""))
122
- assert_equal("1,2,3,", s >= (t1_proc >= t2 >= ""))
123
- assert_equal("1,2,3,", s >= (t1_proc >= t2) >= "")
124
- assert_equal("1,2,3,", s >= ((t1_proc >= t2) >= ""))
125
-
126
- assert_equal("1,2,3,", "" <= t2 <= t1_proc <= s)
127
- assert_equal("1,2,3,", "" <= t2 <= (t1_proc <= s))
128
- assert_equal("1,2,3,", "" <= (t2 <= t1_proc <= s))
129
- assert_equal("1,2,3,", "" <= (t2 <= t1_proc) <= s)
130
- assert_equal("1,2,3,", "" <= ((t2 <= t1_proc) <= s))
131
-
132
- assert_equal("1,2,3,", s >= t1 >= t2_proc >= "")
133
- assert_equal("1,2,3,", s >= t1 >= (t2_proc >= ""))
134
- assert_equal("1,2,3,", s >= (t1 >= t2_proc >= ""))
135
- assert_equal("1,2,3,", s >= (t1 >= t2_proc) >= "")
136
- assert_equal("1,2,3,", s >= ((t1 >= t2_proc) >= ""))
137
-
138
- assert_equal("1,2,3,", "" <= t2_proc <= t1 <= s)
139
- assert_equal("1,2,3,", "" <= t2_proc <= (t1 <= s))
140
- assert_equal("1,2,3,", "" <= (t2_proc <= t1 <= s))
141
- assert_equal("1,2,3,", "" <= (t2_proc <= t1) <= s)
142
- assert_equal("1,2,3,", "" <= ((t2_proc <= t1) <= s))
143
- end
144
-
145
- def test_transformer_proc_filter
146
- assert_equal("2468", (1..9) >= proc{|x| x.to_s if x.even? } >= "")
147
- assert_equal("2468", (1..9) >= proc{|x| x if x.even? } >= proc{|x| x.to_s} >= "")
148
- assert_equal("56789", (5..15) >= proc{|x| x.to_s } >= proc{|s| s if s.length == 1 } >= "")
149
- end
150
-
151
- def test_multiple_args
152
- assert_equal([[1,2],[3,4]], [[1,2],[3,4]] >= proc{|a,b| [a,b]} >= [])
153
- end
154
- end