functional-ruby 0.7.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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