functional-ruby 0.7.4 → 0.7.5

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: 1033510698e79e8472d8fee1bc631b9d03edce14
4
- data.tar.gz: f3fbc19a3c18543984cba90b64ac8508b105bd63
3
+ metadata.gz: 97e8113fb43fd8fda7f581f0e3bc342e66b42cdb
4
+ data.tar.gz: c64e9d6a0788814d50bdc87b00ca7d00caa887a5
5
5
  SHA512:
6
- metadata.gz: 4d206b9ddcc7ab49f2225e8fe9a3fb638d9b35ab09a75ee6a33602d000003cf235dab1650fcc8e72accfaed3371b7254104bbae4227db16c64eaf4ea15cacb37
7
- data.tar.gz: d90c081ad4efc3963d4e125d9c1867c138067d096e136c0fb0ad9d124f8438415d098a36847e877ccf9186df81906f495cecebcff55bdde7885dfb2af403a0e2
6
+ metadata.gz: ac1e57dac94eedaf8a94a45715ecc5757238afa8a7fe4dc24e57b3255d1ce7f1e64a3b2d562c099d0b77ca1b815778905ad561c09e46c337c1b43c0e4c4188f2
7
+ data.tar.gz: 4db984862cf3b281ed4251e4fd1d6f8702e015f47a348d091080f84df0479cdc3fa77ecc2eafb0c64801c7e7d8a58ca8d7a3f1dd8ef7e1006359f02e498f94d6
data/README.md CHANGED
@@ -16,21 +16,50 @@ The project is hosted on the following sites:
16
16
 
17
17
  ## Introduction
18
18
 
19
- Three things I love are [Ruby](http://www.ruby-lang.org/en/),
19
+ Two things I love are [Ruby](http://www.ruby-lang.org/en/) and
20
20
  [functional](https://en.wikipedia.org/wiki/Functional_programming)
21
- [programming](http://c2.com/cgi/wiki?FunctionalProgramming) and
22
- [concurrency](http://www.amazon.com/s/ref=nb_sb_noss_1?url=search-alias%3Dstripbooks&field-keywords=concurrent%20programming).
23
- Sadly, the first is generally not associated with the other two. First, I reject the
24
- assertion that Ruby is an object-oriented language. It's certainly object-based, since
21
+ [programming](http://c2.com/cgi/wiki?FunctionalProgramming).
22
+ Sadly, the former is generally not associated with the latter. Unfortunately,
23
+ too many people are blinded by their belief that Ruby is an object-oriented
24
+ language. I reject this assertion. Ruby is certainly object-based, since
25
25
  everything is an object, but entire large-scale programs can be built without ever
26
- defining a single class. Ruby is a true multi-paradigm language and easily supports
27
- many advanced functional techniques. As to concurrency, Ruby's bad reputation is
28
- well earned, but recent versions of Ruby have made significan improvements in that
29
- area. Ruby 2.0 is now a [relevant](https://blog.heroku.com/archives/2013/6/17/ruby-2-default-new-aps)
30
- platform for concurrent applications.
26
+ defining a single class. But Ruby's features that support functional programming
27
+ don't stop there.
28
+
29
+ Ask ten different programmers to define the term "functional programming" and
30
+ you will likely get ten different answers. One characteristic that will certainly
31
+ be on all their lists is support for
32
+ [higher](http://en.wikipedia.org/wiki/Higher-order_function)
33
+ [order](http://learnyouahaskell.com/higher-order-functions)
34
+ [functions](http://learnyousomeerlang.com/higher-order-functions). Put simply, a
35
+ higher order function is any function that can take one or more functions as
36
+ parameters and/or return a function as a result. Many functional languages,
37
+ such as Erlang, Haskell, and Closure, support higher order functions. Higher order
38
+ functions are a remarkable tool that can completely change the was software is
39
+ designed. Most classicaly object-oriented languages do not support higher
40
+ order functions. Unfortunately, Ruby does not directly support higher order
41
+ functions. Thanksfully, Ruby *does* give us blocks, `proc`s, and `lambda`s.
42
+ Though not strictly higher order functions, in most cases they are functionally
43
+ equivalent.
44
+
45
+ If you combine Ruby's ability to create functions sans-classes with the power
46
+ of blocks/`proc`s/`lambda`s, Ruby code can follow just about every modern functional
47
+ programming design paradigm. Hence, I consider Ruby to be a *multi-paradigm* language.
48
+ Add to this Ruby's vast metaprogramming capabilities and Ruby is easily one of the
49
+ most powerful languages in common use today.
31
50
 
32
51
  This gem is my small and humble attempt to help Ruby reach its full potential as
33
- a highly performant, functional programming language.
52
+ a highly performant, functional programming language. Virtually every function in
53
+ this library takes a block parameter. Some allow a block plus one or more `proc`
54
+ arguments. Most operate *on* data structures rather than being buried *in* data
55
+ structures. Finally, several of the tools in this library are Ruby implementations
56
+ of some of my favorite features from other functional programming languages. Not
57
+ every function is pure, but functions with side effects are easy to spot because
58
+ they almost always have names that end in an exclamation point.
59
+
60
+ My hope is that this gem will help Ruby programmers explore Ruby as a functional
61
+ language and improve their code in ways our object oriented brethern never
62
+ dreamed possible.
34
63
 
35
64
  ### Goals
36
65
 
@@ -48,8 +77,13 @@ My goal is to implement various functional programming patterns in Ruby. Specifi
48
77
 
49
78
  Several features from Erlang, Go, and Clojure have been implemented thus far:
50
79
 
51
- * Function overloading with Erlang-style [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
52
80
  * Interface specifications with Erlang-style [Behavior](https://github.com/jdantonio/functional-ruby/blob/master/md/behavior.md)
81
+ * A [Catalog](https://github.com/jdantonio/functional-ruby/blob/master/md/catalog.md) class for managing sets of key/value pairs in a manner similar to Erlang's [proplists](http://www.erlang.org/doc/man/proplists.html)
82
+ * A toolkit of [collection](https://github.com/jdantonio/functional-ruby/blob/master/md/collection.md) utilities for operating on list-like data structures
83
+ * A set of string [inflections](https://github.com/jdantonio/functional-ruby/blob/master/md/inflect.md) borrowed from [Active Support](http://guides.rubyonrails.org/active_support_core_extensions.html#inflections)
84
+ * Function overloading with Erlang-style [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
85
+ * Tools for introspecting the runtime [Platform](https://github.com/jdantonio/functional-ruby/blob/master/md/platform.md) for information about the operating system and Ruby version
86
+ * [Search](https://github.com/jdantonio/functional-ruby/blob/master/md/search.md) and [sort](https://github.com/jdantonio/functional-ruby/blob/master/md/sort.md) algorithms like you remember from your algorithms class, but with a functional twist
53
87
  * Several useful functional [Utilities](https://github.com/jdantonio/functional-ruby/blob/master/md/utilities.md)
54
88
 
55
89
  ### Is it any good?
@@ -1,5 +1,24 @@
1
1
  require 'functional/behavior'
2
+ require 'functional/behaviour'
3
+ require 'functional/catalog'
4
+ require 'functional/collection'
5
+ require 'functional/inflect'
2
6
  require 'functional/pattern_matching'
3
7
  require 'functional/platform'
8
+ require 'functional/search'
9
+ require 'functional/sort'
4
10
  require 'functional/utilities'
5
11
  require 'functional/version'
12
+
13
+ Infinity = 1/0.0 unless defined?(Infinity)
14
+ NaN = 0/0.0 unless defined?(NaN)
15
+
16
+ module Functional
17
+
18
+ class << self
19
+ include Collection
20
+ include Inflect
21
+ include Search
22
+ include Sort
23
+ end
24
+ end
@@ -0,0 +1,487 @@
1
+ module Functional
2
+
3
+ # A collection of key/value pairs similar to a hash but ordered.
4
+ # Access is via index (like an array) rather than by key (like a
5
+ # hash). Supports duplicate keys. Indexing starts at zero.
6
+ class Catalog
7
+ include Enumerable
8
+
9
+ # Create a new Catalog from the given data. When +data+ is nil
10
+ # or an empty collection the resulting Catalog will be empty.
11
+ # When +data+ is an array, hash, or catalog array the appropriate
12
+ # +#from_+ factory method will be called. The +:from+ option is
13
+ # used to indicate the type of the source data.
14
+ #
15
+ # If a block is given each value in the from the source array will
16
+ # be passed to the block and the result will be stored as the value
17
+ # in the Catalog.
18
+ #
19
+ # @param [Array, Hash, Catalog] data the data to construct the
20
+ # Catalog from
21
+ # @param [Hash] opts processing options
22
+ #
23
+ # @option opts [Symbol] :from the type of the data source. Valid values
24
+ # are :catalog/:catalogue, :hash, :array (default :catalog).
25
+ def initialize(data=nil, opts={})
26
+
27
+ if block_given?
28
+
29
+ @data = []
30
+ data.each do |item|
31
+ @data << yield(item)
32
+ end
33
+
34
+ else
35
+ from = opts[:from]
36
+ from = :array if [:set, :list, :stack, :queue, :vector].include?(from)
37
+ from = "from_#{from}".to_sym
38
+
39
+ if Catalog.respond_to?(from)
40
+ @data = Catalog.send(from, data)
41
+ @data = @data.instance_variable_get(:@data)
42
+ elsif opts[:from].nil? && !data.nil?
43
+ @data = Catalog.from_catalog(data)
44
+ @data = @data.instance_variable_get(:@data)
45
+ else
46
+ @data = []
47
+ end
48
+ end
49
+ end
50
+
51
+ # Creates a new Catalog object from a hash. Each key/value pair in the
52
+ # hash will be converted to a key/value array in the new Catalog. If a
53
+ # block is given each value in the array will be passed to the block
54
+ # and the result will be stored as the value in the Catalog.
55
+ def self.from_hash(data = {})
56
+ collected = []
57
+ data.each do |key, value|
58
+ value = yield(value) if block_given?
59
+ collected << [key, value]
60
+ end
61
+ catalog = Catalog.new
62
+ catalog.instance_variable_set(:@data, collected)
63
+ return catalog
64
+ end
65
+
66
+ # Creates a new catalog object from an array. Each successive pair of
67
+ # elements will become a key/value pair in the new Catalog. If the source
68
+ # array has an odd number of elements the last element will be discarded.
69
+ # If a block is given each element in the source array will be passed to
70
+ # the block and the result will be stored in the new Catalog.
71
+ def self.from_array(*args)
72
+ collected = []
73
+ data = args.flatten
74
+
75
+ max = ((data.size % 2 == 0) ? data.size-1 : data.size-2)
76
+ (0..max).step(2) do |index|
77
+ key = block_given? ? yield(data[index]) : data[index]
78
+ value = block_given? ? yield(data[index+1]) : data[index+1]
79
+ collected << [key, value]
80
+ end
81
+
82
+ catalog = Catalog.new
83
+ catalog.instance_variable_set(:@data, collected)
84
+ return catalog
85
+ end
86
+
87
+ # Creates a new Catalog object from an array of key/value pairs.
88
+ # Each key/value pair in the source array will be stored in the new
89
+ # Catalog. If a block is given each value in the from the source array
90
+ # will be passed to the block and the result will be stored as the
91
+ # value in the Catalog.
92
+ def self.from_catalog(data, *args)
93
+ collected = []
94
+
95
+ if args.empty? && data.size == 2 && !data.first.is_a?(Array)
96
+ # Catalog.from_catalog([:one, 1])
97
+ data = [data]
98
+ elsif !args.empty?
99
+ #Catalog.from_catalog([:one, 1], [:two, 2], [:three, 3])
100
+ data = [data] + args
101
+ end
102
+
103
+ data.each do |item|
104
+ if block_given?
105
+ collected << [item.first, yield(item.last)]
106
+ else
107
+ collected << item
108
+ end
109
+ end
110
+
111
+ catalog = Catalog.new
112
+ catalog.instance_variable_set(:@data, collected)
113
+ return catalog
114
+ end
115
+
116
+ class << self
117
+ alias :from_catalogue :from_catalog
118
+ end
119
+
120
+ # Returns true if self array contains no elements.
121
+ def empty?
122
+ size == 0
123
+ end
124
+
125
+ # Returns the number of elements in self. May be zero.
126
+ def length
127
+ @data.length
128
+ end
129
+
130
+ alias :size :length
131
+
132
+ # Returns the first element, or the first n elements, of the array.
133
+ # If the array is empty, the first form returns nil, and the second
134
+ # form returns an empty array.
135
+ def first
136
+ @data.first
137
+ end
138
+
139
+ # Returns the last element(s) of self. If the array is empty,
140
+ # the first form returns nil.
141
+ def last
142
+ @data.last
143
+ end
144
+
145
+ # Equality—Two arrays are equal if they contain the same number of
146
+ # elements and if each element is equal to (according to Object.==)
147
+ # the corresponding element in the other array.
148
+ def ==(other)
149
+ if other.is_a? Catalog
150
+ return (@data == other.instance_variable_get(:@data))
151
+ elsif other.is_a? Array
152
+ return (@data == other)
153
+ else
154
+ return false
155
+ end
156
+ end
157
+
158
+ alias :eql? :==
159
+
160
+ # Comparison—Returns an integer (-1, 0, or +1) if this array is less
161
+ # than, equal to, or greater than other_ary. Each object in each
162
+ # array is compared (using <=>). If any value isn’t equal, then that
163
+ # inequality is the return value. If all the values found are equal,
164
+ # then the return is based on a comparison of the array lengths. Thus,
165
+ # two arrays are “equal” according to Array#<=> if and only if they have
166
+ # the same length and the value of each element is equal to the value of
167
+ # the corresponding element in the other array.
168
+ def <=>(other)
169
+ other = other.instance_variable_get(:@data) if other.is_a?(Catalog)
170
+ if other.is_a? Array
171
+ return @data <=> other
172
+ else
173
+ raise TypeError.new("can't convert #{other.class} into Catalog")
174
+ end
175
+ end
176
+
177
+ alias :compare :<=>
178
+ alias :compare_to :<=>
179
+
180
+ # Returns a new array populated with the given objects.
181
+ def [](index)
182
+ datum = @data[index]
183
+ return (datum.nil? ? nil : datum.dup)
184
+ end
185
+
186
+ alias :at :[]
187
+
188
+ # Element Assignment—Sets the element at index, or replaces a subarray starting
189
+ # at start and continuing for length elements, or replaces a subarray specified
190
+ # by range. If indices are greater than the current capacity of the array, the
191
+ # array grows automatically. A negative indices will count backward from the end
192
+ # of the array. Inserts elements if length is zero. An IndexError is raised if a
193
+ # negative index points past the beginning of the array. See also Array#push,
194
+ # and Array#unshift.
195
+ def []=(index, value)
196
+ if (index >= 0 && index >= @data.size) || (index < 0 && index.abs > @data.size)
197
+ raise ArgumentError.new('index must reference an existing element')
198
+ elsif value.is_a?(Hash) && value.size == 1
199
+ @data[index] = [value.keys.first, value.values.first]
200
+ elsif value.is_a?(Array) && value.size == 2
201
+ @data[index] = value.dup
202
+ else
203
+ raise ArgumentError.new('value must be a one-element hash or a two-element array')
204
+ end
205
+ end
206
+
207
+ # Returns a string representation of Catalog.
208
+ def to_s
209
+ return @data.to_s
210
+ end
211
+
212
+ # Set Intersection—Returns a new array containing elements common to the two
213
+ # arrays, with no duplicates.
214
+ def &(other)
215
+ other = other.instance_variable_get(:@data) if other.is_a?(Catalog)
216
+ if other.is_a? Array
217
+ return Catalog.from_catalog(@data & other)
218
+ else
219
+ raise TypeError.new("can't convert #{other.class} into Catalog")
220
+ end
221
+ end
222
+
223
+ alias :intersection :&
224
+
225
+ # Concatenation—Returns a new array built by concatenating the two arrays
226
+ # together to produce a third array.
227
+ def +(other)
228
+ other = other.instance_variable_get(:@data) if other.is_a?(Catalog)
229
+ if other.is_a? Array
230
+ return Catalog.from_catalog(@data + other)
231
+ else
232
+ raise TypeError.new("can't convert #{other.class} into Catalog")
233
+ end
234
+ end
235
+
236
+ alias :add :+
237
+ alias :sum :+
238
+
239
+ # Set Union—Returns a new array by joining this array with other_array,
240
+ # removing duplicates.
241
+ def |(other)
242
+ other = other.instance_variable_get(:@data) if other.is_a?(Catalog)
243
+ if other.is_a? Array
244
+ return Catalog.from_catalog(@data | other)
245
+ else
246
+ raise TypeError.new("can't convert #{other.class} into Catalog")
247
+ end
248
+ end
249
+
250
+ alias :union :|
251
+
252
+ # Append—Pushes the given object(s) on to the end of this array.
253
+ # This expression returns the array itself, so several appends
254
+ # may be chained together.
255
+ def push(item)
256
+ if item.is_a?(Hash) && item.size == 1
257
+ @data << [item.keys.first, item.values.first]
258
+ return self
259
+ elsif item.is_a?(Array) && item.size == 2
260
+ @data << item
261
+ return self
262
+ else
263
+ raise TypeError.new("can't convert #{item.class} into Catalog")
264
+ end
265
+ end
266
+
267
+ alias :<< :push
268
+ alias :append :push
269
+
270
+ # Removes the last element from self and returns it, or nil if the
271
+ # Catalog is empty.
272
+ def pop
273
+ if self.empty?
274
+ return nil
275
+ else
276
+ return @data.pop
277
+ end
278
+ end
279
+
280
+ # Copies the last element from self and returns it, or nil if the
281
+ # Catalog is empty.
282
+ def peek
283
+ if self.empty?
284
+ return nil
285
+ else
286
+ return @data.last.dup
287
+ end
288
+ end
289
+
290
+ # Returns a new array populated with the keys from this hash.
291
+ # See also Hash#values.
292
+ def keys
293
+ return @data.collect{|item| item.first}
294
+ end
295
+
296
+ # Returns a new array populated with the values from hsh.
297
+ # See also Hash#keys.
298
+ def values
299
+ return @data.collect{|item| item.last}
300
+ end
301
+
302
+ # Calls block once for each key in hsh, passing the key and value
303
+ # to the block as a two-element array. Because of the assignment
304
+ # semantics of block parameters, these elements will be split out
305
+ # if the block has two formal parameters. Also see Hash.each_pair,
306
+ # which will be marginally more efficient for blocks with two
307
+ # parameters.
308
+ def each(&block)
309
+ @data.each do |item|
310
+ yield(item)
311
+ end
312
+ end
313
+
314
+ # Calls block once for each key in hsh, passing the key and value as parameters.
315
+ def each_pair(&block)
316
+ @data.each do |item|
317
+ yield(item.first, item.last)
318
+ end
319
+ end
320
+
321
+ # Calls block once for each key in hsh, passing the key as a parameter.
322
+ def each_key(&block)
323
+ @data.each do |item|
324
+ yield(item.first)
325
+ end
326
+ end
327
+
328
+ # Calls block once for each key in hsh, passing the value as a parameter.
329
+ def each_value(&block)
330
+ @data.each do |item|
331
+ yield(item.last)
332
+ end
333
+ end
334
+
335
+ # Returns true if the given object is present in self (that is,
336
+ # if any object == anObject), false otherwise.
337
+ def include?(key=nil, value=nil)
338
+ if key && value
339
+ return @data.include?([key, value])
340
+ elsif key.is_a?(Array)
341
+ return @data.include?(key)
342
+ elsif key.is_a?(Hash) && key.size == 1
343
+ return @data.include?([key.keys.first, key.values.first])
344
+ else
345
+ return false
346
+ end
347
+ end
348
+
349
+ # Element Reference—Returns the element at index, or returns a
350
+ # subarray starting at start and continuing for length elements,
351
+ # or returns a subarray specified by range. Negative indices count
352
+ # backward from the end of the array (-1 is the last element).
353
+ # Returns nil if the index (or starting index) are out of range.
354
+ def slice(index, length=nil)
355
+ if length.nil?
356
+ catalog = @data.slice(index)
357
+ else
358
+ catalog = @data.slice(index, length)
359
+ end
360
+ return Catalog.new(catalog)
361
+ end
362
+
363
+ # Deletes the element(s) given by an index (optionally with a length)
364
+ # or by a range. Returns the deleted object, subarray, or nil if the
365
+ # index is out of range.
366
+ def slice!(index, length=nil)
367
+ if length.nil?
368
+ catalog = @data.slice!(index)
369
+ else
370
+ catalog = @data.slice!(index, length)
371
+ end
372
+ return Catalog.new(catalog)
373
+ end
374
+
375
+ # Return a new Catalog created by sorting self according to the natural
376
+ # sort order of the keys.
377
+ def sort_by_key
378
+ sorted = @data.sort{|a, b| a.first <=> b.first}
379
+ return Catalog.new(sorted)
380
+ end
381
+
382
+ # Sort self according to the natural sort order of the keys. Returns self.
383
+ def sort_by_key!
384
+ sorted = @data.sort!{|a, b| a.first <=> b.first}
385
+ return self
386
+ end
387
+
388
+ # Return a new Catalog created by sorting self according to the natural
389
+ # sort order of the values.
390
+ def sort_by_value
391
+ sorted = @data.sort{|a, b| a.last <=> b.last}
392
+ return Catalog.new(sorted)
393
+ end
394
+
395
+ # Sort self according to the natural sort order of the values. Returns self.
396
+ def sort_by_value!
397
+ sorted = @data.sort!{|a, b| a.last <=> b.last}
398
+ return self
399
+ end
400
+
401
+ # Returns a new array created by sorting self. Comparisons for
402
+ # the sort will be done using the <=> operator or using an
403
+ # optional code block. The block implements a comparison between
404
+ # a and b, returning -1, 0, or +1. See also Enumerable#sort_by.
405
+ def sort(&block)
406
+ sorted = @data.sort(&block)
407
+ return Catalog.new(sorted)
408
+ end
409
+
410
+ # Sorts self. Comparisons for the sort will be done using the <=>
411
+ # operator or using an optional code block. The block implements a
412
+ # comparison between a and b, returning -1, 0, or +1.
413
+ # See also Enumerable#sort_by.
414
+ def sort!(&block)
415
+ sorted = @data.sort!(&block)
416
+ return self
417
+ end
418
+
419
+ # Returns a new array that is a one-dimensional flattening of self.
420
+ def to_a
421
+ return @data.flatten
422
+ end
423
+
424
+ # Returns a new hash by converting each key/value pair in self into
425
+ # a key/value pair in the hash. When duplicate keys are encountered
426
+ # the last value associated with that key is kept and the others are
427
+ # discarded.
428
+ def to_hash
429
+ catalog = {}
430
+ @data.each do |item|
431
+ catalog[item.first] = item.last
432
+ end
433
+ return catalog
434
+ end
435
+
436
+ # Returns a new array that is the dat equivalent of self where each
437
+ # key/value pair is an two-element array within the returned array.
438
+ def to_catalog
439
+ return @data.dup
440
+ end
441
+
442
+ alias :to_catalogue :to_catalog
443
+
444
+ # Deletes items from self that are equal to obj. If the item is
445
+ # not found, returns nil. If the optional code block is given,
446
+ # returns the result of block if the item is not found.
447
+ def delete(key, value=nil)
448
+ item = nil
449
+
450
+ if key && value
451
+ item = @data.delete([key, value])
452
+ elsif key.is_a? Array
453
+ item = @data.delete(key)
454
+ elsif key.is_a? Hash
455
+ item = @data.delete([key.keys.first, key.values.first])
456
+ end
457
+
458
+ item = yield if item.nil? && block_given?
459
+ return item
460
+ end
461
+
462
+ # Deletes the element at the specified index, returning that element,
463
+ # or nil if the index is out of range. See also Array#slice!.
464
+ def delete_at(index)
465
+ item = @data.delete_at(index)
466
+ item = yield if item.nil? && block_given?
467
+ return item
468
+ end
469
+
470
+ # Deletes every element of self for which block evaluates to true.
471
+ def delete_if(&block)
472
+ raise ArgumentError.new('no block supplied') unless block_given?
473
+ if block.arity <= 1
474
+ items = @data.delete_if(&block)
475
+ else
476
+ items = []
477
+ @data.each do |key, value|
478
+ items << [key, value] if yield(key, value)
479
+ end
480
+ items.each {|item| @data.delete(item)}
481
+ end
482
+ return self
483
+ end
484
+ end
485
+
486
+ class Catalogue < Catalog; end
487
+ end