immutable 0.1.0 → 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.
data/README.md CHANGED
@@ -3,37 +3,47 @@ immutable - immutable data structures for Ruby
3
3
 
4
4
  This project aims to provide immutable data structures for Ruby.
5
5
 
6
- Currently, immutable provides the following classes:
6
+ Currently, immutable provides the following classes and modules:
7
7
 
8
+ * Immutable::Foldable
8
9
  * Immutable::List
9
10
  * Immutable::Map
11
+ * Immutable::Promise
12
+ * Immutable::Stream
13
+ * Immutable::Queue
10
14
 
11
15
  Install
12
16
  ========================
13
17
 
14
18
  $ gem install immutable
15
19
 
20
+ Documentation
21
+ ========================
22
+
23
+ * [API Reference](http://rubydoc.info/github/shugo/immutable/frames)
24
+
16
25
  License
17
26
  ========================
18
- (The MIT License)
19
-
20
- Copyright (c) 2012 Shugo Maeda
21
-
22
- Permission is hereby granted, free of charge, to any person obtaining
23
- a copy of this software and associated documentation files (the
24
- 'Software'), to deal in the Software without restriction, including
25
- without limitation the rights to use, copy, modify, merge, publish,
26
- distribute, sublicense, and/or sell copies of the Software, and to
27
- permit persons to whom the Software is furnished to do so, subject to
28
- the following conditions:
29
-
30
- The above copyright notice and this permission notice shall be
31
- included in all copies or substantial portions of the Software.
32
-
33
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
34
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
35
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
36
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
37
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
38
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
39
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ (The MIT License)
29
+
30
+ Copyright (c) 2012 Shugo Maeda
31
+
32
+ Permission is hereby granted, free of charge, to any person obtaining
33
+ a copy of this software and associated documentation files (the
34
+ 'Software'), to deal in the Software without restriction, including
35
+ without limitation the rights to use, copy, modify, merge, publish,
36
+ distribute, sublicense, and/or sell copies of the Software, and to
37
+ permit persons to whom the Software is furnished to do so, subject to
38
+ the following conditions:
39
+
40
+ The above copyright notice and this permission notice shall be
41
+ included in all copies or substantial portions of the Software.
42
+
43
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
44
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
45
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
46
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
47
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
48
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
49
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,41 @@
1
+ require 'benchmark'
2
+ require 'immutable/queue'
3
+ require 'immutable/map'
4
+
5
+ TIMES = 100000
6
+ key_size = 10
7
+
8
+ def snoc(queue)
9
+ TIMES.times do
10
+ queue.snoc(0)
11
+ end
12
+ end
13
+
14
+ def head(queue)
15
+ TIMES.times do
16
+ queue.head
17
+ end
18
+ end
19
+
20
+ def tail(queue)
21
+ TIMES.times do
22
+ queue.tail
23
+ end
24
+ end
25
+
26
+ def run(bm, queue, num, method)
27
+ bm.report("#{method} for #{num}-elements queue") do
28
+ send(method, queue)
29
+ end
30
+ end
31
+
32
+ Benchmark.bmbm do |bm|
33
+ queues = [10, 1000, 100000].inject(Immutable::Map.empty) { |m, n|
34
+ m.insert(n, (1..n).inject(Immutable::Queue.empty, &:snoc))
35
+ }
36
+ for method in [:snoc, :head, :tail]
37
+ for num, queue in queues
38
+ run(bm, queue, num, method)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,9 @@
1
+ # +Immutable+ is a namespace for immutable data structures.
2
+ module Immutable
3
+ end
4
+
5
+ require "immutable/list"
6
+ require "immutable/map"
7
+ require "immutable/promise"
8
+ require "immutable/stream"
9
+ require "immutable/queue"
@@ -0,0 +1,29 @@
1
+ module Immutable
2
+ module Foldable
3
+ # Returns the number of elements in +self+. May be zero.
4
+ #
5
+ # @return [Integer] the number of elements in +self+.
6
+ def length
7
+ foldl(0) { |x, y| x + 1 }
8
+ end
9
+
10
+ # Returns the number of elements in +self+. May be zero.
11
+ #
12
+ # @return [Integer] the number of elements in +self+.
13
+ alias size length
14
+
15
+ # Computes the sum of the numbers in +self+.
16
+ #
17
+ # @return [#+] the sum of the numbers.
18
+ def sum
19
+ foldl(0, &:+)
20
+ end
21
+
22
+ # Computes the product of the numbers in +self+.
23
+ #
24
+ # @return [#*] the product of the numbers.
25
+ def product
26
+ foldl(1, &:*)
27
+ end
28
+ end
29
+ end
@@ -1,121 +1,167 @@
1
- # -*- tailcall-optimization: true; trace-instruction: false -*-
1
+ require "immutable/foldable"
2
2
 
3
3
  module Immutable
4
- # <code>Immutable::List</code> represents an immutable list.
4
+ # +Immutable::List+ represents an immutable list.
5
5
  #
6
- # <code>Immutable::List</code> is an abstract class and
7
- # <code>Immutable::List.[]</code> should be used instead of
8
- # <code>Immutable::List.new</code>. For example:
6
+ # +Immutable::List+ is an abstract class and
7
+ # {Immutable::List.[]} should be used instead of
8
+ # {Immutable::List.new}. For example:
9
9
  #
10
10
  # include Immutable
11
11
  # p List[] #=> List[]
12
12
  # p List[1, 2] #=> List[1, 2]
13
13
  #
14
- # <code>Immutable::Nil</code> represents an empty list, and
15
- # <code>Immutable::Cons</code> represents a cons cell, which is a node in
14
+ # +Immutable::Nil+ represents an empty list, and
15
+ # +Immutable::Cons+ represents a cons cell, which is a node in
16
16
  # a list. For example:
17
17
  #
18
18
  # p Nil #=> List[]
19
19
  # p Cons[1, Cons[2, Nil]] #=> List[1, 2]
20
20
  class List
21
+ include Enumerable
22
+ include Foldable
23
+
21
24
  class EmptyError < StandardError
22
25
  def initialize(msg = "list is empty")
23
26
  super(msg)
24
27
  end
25
28
  end
26
29
 
27
- # Returns a new list populated with the given objects.
28
- def self.[](*args)
29
- from_array(args)
30
+ # Creates a new list populated with the given objects.
31
+ #
32
+ # @param [Array<Object>] elements the elements of the list.
33
+ # @return [List] the new list.
34
+ def self.[](*elements)
35
+ from_array(elements)
30
36
  end
31
37
 
32
- # Converts the given array +ary+ to a list.
38
+ # Converts the given array to a list.
39
+ #
40
+ # @param [Array, #reverse_each] ary the array to convert.
41
+ # @return [List] the list converted from +ary+.
33
42
  def self.from_array(ary)
34
43
  ary.reverse_each.inject(Nil) { |x, y|
35
44
  Cons.new(y, x)
36
45
  }
37
46
  end
38
47
 
39
- # Converts +enum+ to a list. +enum+ should respond to <code>each</code>.
48
+ # Converts the given Enumerable object to a list.
49
+ #
50
+ # @param [#inject] enum the Enumerable object to convert.
51
+ # @return [List] the list converted from +enum+.
40
52
  def self.from_enum(enum)
41
53
  enum.inject(Nil) { |x, y|
42
54
  Cons.new(y, x)
43
55
  }.reverse
44
56
  end
45
57
 
58
+ # Calls +block+ once for each element in +self+.
59
+ def each(&block)
60
+ end
61
+
46
62
  # Appends two lists +self+ and +xs+.
63
+ #
64
+ # @param [List] xs the list to append.
65
+ # @return [List] the new list.
47
66
  def +(xs)
48
67
  foldr(xs) { |y, ys| Cons[y, ys] }
49
68
  end
50
69
 
70
+ # Returns whether +self+ equals to +xs+.
71
+ #
72
+ # @param [List] xs the list to compare.
73
+ # @return [true, false] +true+ if +self+ equals to +xs+; otherwise,
74
+ # +false+.
75
+ def ==(xs)
76
+ # this method should be overriden
77
+ end
78
+
51
79
  # Returns the first element of +self+. If +self+ is empty,
52
- # <code>Immutable::List::EmptyError</code> is raised.
80
+ # +Immutable::List::EmptyError+ is raised.
81
+ #
82
+ # @return [Object] the first element of +self+.
53
83
  def head
54
- raise ScriptError, "this method should be overriden"
84
+ # this method should be overriden
55
85
  end
56
86
 
57
87
  # Returns the first element of +self+. If +self+ is empty,
58
- # <code>Immutable::List::EmptyError</code> is raised.
88
+ # +Immutable::List::EmptyError+ is raised.
89
+ #
90
+ # @return [Object] the first element of +self+.
59
91
  def first
60
92
  head
61
93
  end
62
94
 
63
95
  # Returns the last element of +self+. If +self+ is empty,
64
- # <code>Immutable::List::EmptyError</code> is raised.
96
+ # +Immutable::List::EmptyError+ is raised.
97
+ #
98
+ # @return [Object] the last element of +self+.
65
99
  def last
66
- raise ScriptError, "this method should be overriden"
100
+ # this method should be overriden
67
101
  end
68
102
 
69
- # Returns the element after the head of +self+. If +self+ is empty,
70
- # <code>Immutable::List::EmptyError</code> is raised.
103
+ # Returns the elements after the head of +self+. If +self+ is empty,
104
+ # +Immutable::List::EmptyError+ is raised.
105
+ #
106
+ # @return [List] the elements after the head of +self+.
71
107
  def tail
72
- raise ScriptError, "this method should be overriden"
108
+ # this method should be overriden
73
109
  end
74
110
 
75
- # Returns all the element of +self+ except the last one.
76
- # If +self+ is empty, <code>Immutable::List::EmptyError</code> is
111
+ # Returns all the elements of +self+ except the last one.
112
+ # If +self+ is empty, +Immutable::List::EmptyError+ is
77
113
  # raised.
114
+ #
115
+ # @return [List] the elements of +self+ except the last one.
78
116
  def init
79
- raise ScriptError, "this method should be overriden"
117
+ # this method should be overriden
80
118
  end
81
119
 
82
- # Returns <code>true</code> if +self+ is empty.
120
+ # Returns whether +self+ is empty.
121
+ #
122
+ # @return [true, false] +true+ if +self+ is empty; otherwise, +false+.
83
123
  def empty?
84
- raise ScriptError, "this method should be overriden"
124
+ # this method should be overriden
85
125
  end
86
126
 
87
- # Returns <code>true</code> if +self+ is empty.
127
+ # Returns whether +self+ is empty.
128
+ #
129
+ # @return [true, false] +true+ if +self+ is empty; otherwise, +false+.
88
130
  def null?
89
131
  empty?
90
132
  end
91
133
 
92
- # Returns the number of elements in +self+. May be zero.
93
- def length
94
- foldl(0) { |x, y| x + 1 }
95
- end
96
-
97
- alias size length
98
-
99
134
  # Returns the list obtained by applying the given block to each element
100
135
  # in +self+.
136
+ #
137
+ # @return [List] the obtained list.
101
138
  def map
102
139
  foldr(Nil) { |x, xs| Cons[yield(x), xs] }
103
140
  end
104
141
 
105
142
  # Returns the elements of +self+ in reverse order.
143
+ #
144
+ # @return [List] the reversed list.
106
145
  def reverse
107
146
  foldl(Nil) { |x, y| Cons[y, x] }
108
147
  end
109
148
 
110
- # Inserts +xs+ in between the lists in +self+.
111
- def intersperse(xs)
112
- raise ScriptError, "this method should be overriden"
149
+ # Returns a new list obtained by inserting +sep+ in between the elements
150
+ # of +self+.
151
+ #
152
+ # @param [Object] sep the object to insert between elements.
153
+ # @return [List] the new list.
154
+ def intersperse(sep)
155
+ # this method should be overriden
113
156
  end
114
157
 
115
- # Inserts +xs+ in between the lists in +self+ and concatenates the
116
- # result.
117
- # <code>xss.intercalate(xs)</code> is equivalent to
118
- # <code>xss.intersperse(xs).flatten</code>.
158
+ # Returns a new list obtained by inserting +xs+ in between the lists in
159
+ # +self+ and concatenates the result.
160
+ # +xss.intercalate(xs)+ is equivalent to
161
+ # +xss.intersperse(xs).flatten+.
162
+ #
163
+ # @param [List] xs the list to insert between lists.
164
+ # @return [List] the new list.
119
165
  def intercalate(xs)
120
166
  intersperse(xs).flatten
121
167
  end
@@ -124,78 +170,91 @@ module Immutable
124
170
  #
125
171
  # p List[List[1, 2, 3], List[4, 5, 6]].transpose
126
172
  # #=> List[List[1, 4], List[2, 5], List[3, 6]]
173
+ #
174
+ # @return [List] the transposed list.
127
175
  def transpose
128
- raise ScriptError, "this method should be overriden"
176
+ # this method should be overriden
177
+ end
178
+
179
+ # Returns the list of all subsequences of +self+.
180
+ #
181
+ # @return [List<List>] the list of subsequences.
182
+ def subsequences
183
+ Cons[List[], nonempty_subsequences]
129
184
  end
130
185
 
131
186
  # Reduces +self+ using +block+ from right to left. +e+ is used as the
132
187
  # starting value. For example:
133
188
  #
134
189
  # List[1, 2, 3].foldr(9) { |x, y| x + y } #=> 1 - (2 - (3 - 9)) = -7
190
+ #
191
+ # @param [Object] e the start value.
192
+ # @return [Object] the reduced value.
135
193
  def foldr(e, &block)
136
- raise ScriptError, "this method should be overriden"
194
+ # this method should be overriden
137
195
  end
138
196
 
139
197
  # Reduces +self+ using +block+ from right to left. If +self+ is empty,
140
- # <code>Immutable::List::EmptyError</code> is raised.
198
+ # +Immutable::List::EmptyError+ is raised.
199
+ #
200
+ # @return [Object] the reduced value.
141
201
  def foldr1(&block)
142
- raise ScriptError, "this method should be overriden"
202
+ # this method should be overriden
143
203
  end
144
204
 
145
205
  # Reduces +self+ using +block+ from left to right. +e+ is used as the
146
206
  # starting value. For example:
147
207
  #
148
208
  # List[1, 2, 3].foldl(9) { |x, y| x + y } #=> ((9 - 1) - 2) - 3 = 3
209
+ #
210
+ # @param [Object] e the start value.
211
+ # @return [Object] the reduced value.
149
212
  def foldl(e, &block)
150
- raise ScriptError, "this method should be overriden"
213
+ # this method should be overriden
151
214
  end
152
215
 
153
216
  # Reduces +self+ using +block+ from left to right. If +self+ is empty,
154
- # <code>Immutable::List::EmptyError</code> is raised.
217
+ # +Immutable::List::EmptyError+ is raised.
218
+ #
219
+ # @return [Object] the reduced value.
155
220
  def foldl1(&block)
156
- raise ScriptError, "this method should be overriden"
221
+ # this method should be overriden
157
222
  end
158
223
 
159
224
  # Concatenates a list of lists.
225
+ #
226
+ # @return [List] the concatenated list.
160
227
  def flatten
161
228
  foldr(Nil) { |x, xs| x + xs }
162
229
  end
163
230
 
164
- # An alias of <code>flatten</code>.
165
231
  alias concat flatten
166
232
 
167
233
  # Returns the list obtained by concatenating the results of the given
168
234
  # block for each element in +self+.
235
+ #
236
+ # @return [List] the obtained list.
169
237
  def flat_map
170
238
  foldr(Nil) { |x, xs| yield(x) + xs }
171
239
  end
172
240
 
173
- # An alias of <code>flat_map</code>.
174
241
  alias concat_map flat_map
175
242
 
176
- # An alias of <code>flat_map</code>.
177
243
  alias bind flat_map
178
244
 
179
- # Computes the sum of the numbers in +self+.
180
- def sum
181
- foldl(0, &:+)
182
- end
183
-
184
- # Computes the product of the numbers in +self+.
185
- def product
186
- foldl(1, &:*)
187
- end
188
-
189
245
  # Builds a list from the seed value +e+ and the given block. The block
190
- # takes a seed value and returns <code>nil</code> if the seed should
191
- # unfold to the empty list, or returns <code>[a, b]</code>, where
192
- # <code>a</code> is the head of the list and <code>b</code> is the next
246
+ # takes a seed value and returns +nil+ if the seed should
247
+ # unfold to the empty list, or returns +[a, b]+, where
248
+ # +a+ is the head of the list and +b+ is the next
193
249
  # seed from which to unfold the tail. For example:
194
250
  #
195
251
  # xs = List.unfoldr(3) { |x| x == 0 ? nil : [x, x - 1] }
196
252
  # p xs #=> List[3, 2, 1]
197
253
  #
198
- # <code>unfoldr</code> is the dual of <code>foldr</code>.
254
+ # +unfoldr+ is the dual of +foldr+.
255
+ #
256
+ # @param [Object] e the seed value.
257
+ # @return [List] the list built from the seed value and the block.
199
258
  def self.unfoldr(e, &block)
200
259
  x = yield(e)
201
260
  if x.nil?
@@ -206,40 +265,68 @@ module Immutable
206
265
  end
207
266
  end
208
267
 
209
- # Returns the first +n+ elements of +self+, or +self+ itself if
210
- # <code>n > self.length</code>.
211
- def take(n)
212
- raise ScriptError, "this method should be overriden"
268
+ # Returns the +n+th element of +self+. If +n+ is out of range, +nil+ is
269
+ # returned.
270
+ #
271
+ # @return [Object] the +n+th element.
272
+ def [](n)
273
+ # this method should be overriden
213
274
  end
214
275
 
276
+ # Returns the first +n+ elements of +self+, or all the elements of
277
+ # +self+ if +n > self.length+.
278
+ #
279
+ # @param [Integer] n the number of elements to take.
280
+ # @return [List] the first +n+ elements of +self+.
281
+ def take(n)
282
+ # this method should be overriden
283
+ end
215
284
 
216
285
  # Returns the suffix of +self+ after the first +n+ elements, or
217
- # <code>List[]</code> if <code>n > self.length</code>.
286
+ # +List[]+ if +n > self.length+.
287
+ #
288
+ # @param [Integer] n the number of elements to drop.
289
+ # @return [List] the suffix of +self+ after the first +n+ elements.
218
290
  def drop(n)
219
- raise ScriptError, "this method should be overriden"
291
+ # this method should be overriden
220
292
  end
221
293
 
222
294
  # Returns the longest prefix of the elements of +self+ for which +block+
223
295
  # evaluates to true.
296
+ #
297
+ # @return [List] the prefix of the elements of +self+.
224
298
  def take_while(&block)
225
- raise ScriptError, "this method should be overriden"
299
+ # this method should be overriden
226
300
  end
227
301
 
228
302
  # Returns the suffix remaining after
229
- # <code>self.take_while(&block)</code>.
303
+ # +self.take_while(&block)+.
304
+ #
305
+ # @return [List] the suffix of the elements of +self+.
230
306
  def drop_while(&block)
231
- raise ScriptError, "this method should be overriden"
307
+ # this method should be overriden
308
+ end
309
+
310
+ # Returns +self+.
311
+ #
312
+ # @return [List] +self+.
313
+ def to_list
314
+ self
232
315
  end
233
316
 
234
317
  # Returns the first element in +self+ for which the given block
235
318
  # evaluates to true. If such an element is not found, it
236
- # returns <code>nil</code>.
319
+ # returns +nil+.
320
+ #
321
+ # @return [Object] the found element.
237
322
  def find(&block)
238
- raise ScriptError, "this method should be overriden"
323
+ # this method should be overriden
239
324
  end
240
325
 
241
326
  # Returns the elements in +self+ for which the given block evaluates to
242
327
  # true.
328
+ #
329
+ # @return [List] the elements that satisfies the condition.
243
330
  def filter
244
331
  foldr(Nil) { |x, xs|
245
332
  if yield(x)
@@ -249,12 +336,38 @@ module Immutable
249
336
  end
250
337
  }
251
338
  end
339
+
340
+ # Takes zero or more lists and returns a new list in which each element
341
+ # is an array of the corresponding elements of +self+ and the input
342
+ # lists.
343
+ #
344
+ # @param [Array<List>] xss the input lists.
345
+ # @return [List] the new list.
346
+ def zip(*xss)
347
+ # this method should be overriden
348
+ end
349
+
350
+ # Takes zero or more lists and returns the list obtained by applying the
351
+ # given block to an array of the corresponding elements of +self+ and
352
+ # the input lists.
353
+ # +xs.zip_with(*yss, &block)+ is equivalent to
354
+ # +xs.zip(*yss).map(&block)+.
355
+ #
356
+ # @param [Array<List>] xss the input lists.
357
+ # @return [List] the new list.
358
+ def zip_with(*xss, &block)
359
+ # this method should be overriden
360
+ end
252
361
  end
253
362
 
363
+ # +Immutable::Nil+ represents an empty list.
254
364
  Nil = List.new
255
365
 
366
+ # +Immutable::Cons+ represents a cons cell.
256
367
  class Cons < List
257
- # Creates a list obtained by prepending +head+ to the list +tail+.
368
+ # Creates a new list obtained by prepending +head+ to the list +tail+.
369
+ #
370
+ # @return [Cons] the new list.
258
371
  def self.[](head, tail = Nil)
259
372
  self.new(head, tail)
260
373
  end
@@ -329,6 +442,14 @@ module Immutable
329
442
  end
330
443
  end
331
444
 
445
+ def Nil.each
446
+ end
447
+
448
+ def each(&block)
449
+ yield(@head)
450
+ @tail.each(&block)
451
+ end
452
+
332
453
  def Nil.foldl(e)
333
454
  e
334
455
  end
@@ -345,8 +466,12 @@ module Immutable
345
466
  @tail.foldl(@head, &block)
346
467
  end
347
468
 
469
+ def Nil.==(xs)
470
+ equal?(xs)
471
+ end
472
+
348
473
  def ==(xs)
349
- if xs.empty?
474
+ if !xs.is_a?(List) || xs.empty?
350
475
  false
351
476
  else
352
477
  @head == xs.head && @tail == xs.tail
@@ -392,6 +517,31 @@ module Immutable
392
517
  end
393
518
  end
394
519
 
520
+ def Nil.nonempty_subsequences
521
+ List[]
522
+ end
523
+
524
+ def nonempty_subsequences
525
+ yss = @tail.nonempty_subsequences.foldr(List[]) { |xs, xss|
526
+ Cons[xs, Cons[Cons[@head, xs], xss]]
527
+ }
528
+ Cons[List[@head], yss]
529
+ end
530
+
531
+ def Nil.[](n)
532
+ nil
533
+ end
534
+
535
+ def [](n)
536
+ if n < 0
537
+ nil
538
+ elsif n == 0
539
+ head
540
+ else
541
+ tail[n - 1]
542
+ end
543
+ end
544
+
395
545
  def Nil.take(n)
396
546
  Nil
397
547
  end
@@ -451,5 +601,25 @@ module Immutable
451
601
  @tail.find(&block)
452
602
  end
453
603
  end
604
+
605
+ def Nil.zip(*xss)
606
+ Nil
607
+ end
608
+
609
+ def zip(*xss)
610
+ heads = xss.map { |xs| xs.null? ? nil : xs.head }
611
+ tails = xss.map { |xs| xs.null? ? Nil : xs.tail }
612
+ Cons[[head, *heads], tail.zip(*tails)]
613
+ end
614
+
615
+ def Nil.zip_with(*xss, &block)
616
+ Nil
617
+ end
618
+
619
+ def zip_with(*xss, &block)
620
+ heads = xss.map { |xs| xs.null? ? nil : xs.head }
621
+ tails = xss.map { |xs| xs.null? ? Nil : xs.tail }
622
+ Cons[yield(head, *heads), tail.zip_with(*tails, &block)]
623
+ end
454
624
  end
455
625
  end