functional-ruby 0.7.7 → 1.0.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -152
  3. data/doc/memo.txt +192 -0
  4. data/doc/pattern_matching.txt +485 -0
  5. data/doc/protocol.txt +221 -0
  6. data/doc/record.txt +144 -0
  7. data/doc/thread_safety.txt +8 -0
  8. data/lib/functional.rb +48 -18
  9. data/lib/functional/abstract_struct.rb +161 -0
  10. data/lib/functional/delay.rb +117 -0
  11. data/lib/functional/either.rb +222 -0
  12. data/lib/functional/memo.rb +93 -0
  13. data/lib/functional/method_signature.rb +72 -0
  14. data/lib/functional/option.rb +209 -0
  15. data/lib/functional/pattern_matching.rb +117 -100
  16. data/lib/functional/protocol.rb +157 -0
  17. data/lib/functional/protocol_info.rb +193 -0
  18. data/lib/functional/record.rb +155 -0
  19. data/lib/functional/type_check.rb +112 -0
  20. data/lib/functional/union.rb +152 -0
  21. data/lib/functional/version.rb +3 -1
  22. data/spec/functional/abstract_struct_shared.rb +154 -0
  23. data/spec/functional/complex_pattern_matching_spec.rb +205 -0
  24. data/spec/functional/configuration_spec.rb +17 -0
  25. data/spec/functional/delay_spec.rb +147 -0
  26. data/spec/functional/either_spec.rb +237 -0
  27. data/spec/functional/memo_spec.rb +207 -0
  28. data/spec/functional/option_spec.rb +292 -0
  29. data/spec/functional/pattern_matching_spec.rb +279 -276
  30. data/spec/functional/protocol_info_spec.rb +444 -0
  31. data/spec/functional/protocol_spec.rb +274 -0
  32. data/spec/functional/record_spec.rb +175 -0
  33. data/spec/functional/type_check_spec.rb +103 -0
  34. data/spec/functional/union_spec.rb +110 -0
  35. data/spec/spec_helper.rb +6 -4
  36. metadata +55 -45
  37. data/lib/functional/behavior.rb +0 -138
  38. data/lib/functional/behaviour.rb +0 -2
  39. data/lib/functional/catalog.rb +0 -487
  40. data/lib/functional/collection.rb +0 -403
  41. data/lib/functional/inflect.rb +0 -127
  42. data/lib/functional/platform.rb +0 -120
  43. data/lib/functional/search.rb +0 -132
  44. data/lib/functional/sort.rb +0 -41
  45. data/lib/functional/utilities.rb +0 -189
  46. data/md/behavior.md +0 -188
  47. data/md/catalog.md +0 -32
  48. data/md/collection.md +0 -32
  49. data/md/inflect.md +0 -32
  50. data/md/pattern_matching.md +0 -512
  51. data/md/platform.md +0 -32
  52. data/md/search.md +0 -32
  53. data/md/sort.md +0 -32
  54. data/md/utilities.md +0 -55
  55. data/spec/functional/behavior_spec.rb +0 -528
  56. data/spec/functional/catalog_spec.rb +0 -1206
  57. data/spec/functional/collection_spec.rb +0 -752
  58. data/spec/functional/inflect_spec.rb +0 -85
  59. data/spec/functional/integration_spec.rb +0 -205
  60. data/spec/functional/platform_spec.rb +0 -501
  61. data/spec/functional/search_spec.rb +0 -187
  62. data/spec/functional/sort_spec.rb +0 -61
  63. data/spec/functional/utilities_spec.rb +0 -277
@@ -1,138 +0,0 @@
1
- module Kernel
2
-
3
- BehaviorError = Class.new(StandardError)
4
-
5
- # Define a behavioral specification (interface).
6
- #
7
- # @param name [Symbol] the name of the behavior
8
- # @param functions [Hash] function names and their arity as key/value pairs
9
- def behavior_info(name, functions = {})
10
- $__behavior_info__ ||= {}
11
- $__behavior_info__[name.to_sym] = functions.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
12
- end
13
-
14
- alias :behaviour_info :behavior_info
15
- alias :interface :behavior_info
16
-
17
- module_function :behavior_info
18
- module_function :behaviour_info
19
- module_function :interface
20
-
21
- # Specify a #behavior_info to enforce on the enclosing class
22
- #
23
- # @param name [Symbol] name of the #behavior_info being implemented
24
- def behavior(name)
25
-
26
- name = name.to_sym
27
- raise BehaviorError.new("undefined behavior '#{name}'") if $__behavior_info__[name].nil?
28
-
29
- clazz = self.method(:behavior).receiver
30
-
31
- unless clazz.instance_methods(false).include?(:behaviors)
32
- class << clazz
33
- def behaviors
34
- @behaviors ||= []
35
- end
36
- end
37
- end
38
-
39
- clazz.behaviors << name
40
-
41
- if self.class == Module
42
- (class << self; self; end).class_eval do
43
- define_method(:included) do |base|
44
- base.class_eval do
45
- behavior(name)
46
- end
47
- end
48
- end
49
- end
50
-
51
- if self.class == Class
52
- unless self.respond_to?(:__new)
53
- class << clazz
54
- alias_method(:__new, :new)
55
- end
56
- end
57
- end
58
-
59
- if $ENABLE_BEHAVIOR_CHECK_ON_CONSTRUCTION == true
60
- class << clazz
61
- def new(*args, &block)
62
- obj = __new(*args, &block)
63
- self.ancestors.each do |clazz|
64
- if clazz.respond_to?(:behaviors)
65
- clazz.behaviors.each do |behavior|
66
- valid = obj.behaves_as?(behavior, true)
67
- end
68
- end
69
- end
70
- return obj
71
- end
72
- end
73
- end
74
- end
75
-
76
- alias :behaviour :behavior
77
- alias :behaves_as :behavior
78
-
79
- module_function :behavior
80
- module_function :behaviour
81
- module_function :behaves_as
82
- end
83
-
84
- class Object
85
-
86
- # Does the object implement the given #behavior_info?
87
- #
88
- # @note Will return true if the object implements the required methods. The
89
- # object's class hierarchy does not necessarily have to include a corresponding
90
- # #behavior call.
91
- #
92
- # @param name [Symbol] name of the #behavior_info to verify behavior against.
93
- # @param abend [Boolean] raise an exception when true and the there are
94
- # unimplemented methods
95
- #
96
- # @return [Boolean] whether or not the required public methods are implemented
97
- def behaves_as?(name, abend = false)
98
-
99
- name = name.to_sym
100
- bi = $__behavior_info__[name]
101
- return false if bi.nil?
102
-
103
- validator = proc do |obj, method, arity|
104
- (obj.respond_to?(method) && arity == :any) || obj.method(method).arity == arity
105
- end
106
-
107
- if self.is_a?(Class) || self.is_a?(Module)
108
- bi = bi.select{|method, arity| method.to_s =~ /^self_/ }
109
- end
110
-
111
- bi.each do |method, arity|
112
- begin
113
- func = method.to_s
114
- obj = self
115
-
116
- if (self.is_a?(Class) || self.is_a?(Module)) && func =~ /^self_/
117
- func = func.gsub(/^self_/, '')
118
- elsif method =~ /^self_/
119
- func = func.gsub(/^self_/, '')
120
- obj = self.class
121
- end
122
-
123
- valid = validator.call(obj, func, arity)
124
- raise NameError if abend && ! valid
125
- return valid unless valid
126
- rescue NameError
127
- if abend
128
- func = "#{method.to_s.gsub(/^self_/, 'self.')}/#{arity.to_s.gsub(/^any$/, ':any')}"
129
- raise BehaviorError.new("undefined callback function ##{func} in #{self} (behavior '#{name}')")
130
- else
131
- return false
132
- end
133
- end
134
- end
135
-
136
- return true
137
- end
138
- end
@@ -1,2 +0,0 @@
1
- # Support the Scandinavian spelling
2
- require_relative 'behavior'
@@ -1,487 +0,0 @@
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