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,403 +0,0 @@
1
- module Functional
2
-
3
- module Collection
4
- extend self
5
-
6
- # Returns a random sample of #size integers between 0 and 100 or
7
- # the provided :min and/or :max options.
8
- #
9
- # @param [Integer] size the size of the sample to create
10
- # @param [Hash] opts processing options
11
- #
12
- # @option opts [Integer] :min the minimum value in the sample
13
- # @option opts [Integer] :max the maximun value in the sample
14
- #
15
- # @return [Array] an array of integers
16
- def random_sample(size, opts={})
17
- min = opts[:min].to_i
18
- max = opts[:max] || 100
19
- sample = []
20
- size.times do
21
- sample << rand(max-min) + min
22
- end
23
- return sample
24
- end
25
-
26
- # Return the index where to insert item x in list a, assuming a is sorted.
27
- #
28
- # The return value i is such that all e in a[:i] have e < x, and all e in
29
- # a[i:] have e >= x. So if x already appears in the list, a.insert(x) will
30
- # insert just before the leftmost x already there.
31
- #
32
- # Optional args lo (default 0) and hi (default len(a)) bound the
33
- # slice of a to be searched.
34
- #
35
- # @see http://docs.python.org/3/library/bisect.html
36
- # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
37
- # @see http://effbot.org/librarybook/bisect.htm
38
- def bisect_left(a, x, opts={})
39
- return nil if a.nil?
40
- return 0 if a.empty?
41
-
42
- lo = (opts[:lo] || opts[:low]).to_i
43
- hi = opts[:hi] || opts[:high] || a.length
44
-
45
- while lo < hi
46
- mid = (lo + hi) / 2
47
- v = (block_given? ? yield(a[mid]) : a[mid])
48
- if v < x
49
- lo = mid + 1
50
- else
51
- hi = mid
52
- end
53
- end
54
- return lo
55
- end
56
-
57
- # Return the index where to insert item x in list a, assuming a is sorted.
58
- #
59
- # The return value i is such that all e in a[:i] have e <= x, and all e in
60
- # a[i:] have e > x. So if x already appears in the list, a.insert(x) will
61
- # insert just after the rightmost x already there.
62
- #
63
- # Optional args lo (default 0) and hi (default len(a)) bound the
64
- # slice of a to be searched.
65
- #
66
- # @see http://docs.python.org/3/library/bisect.html
67
- # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
68
- # @see http://effbot.org/librarybook/bisect.htm
69
- def bisect_right(a, x, opts={})
70
- return nil if a.nil?
71
- return 0 if a.empty?
72
-
73
- lo = (opts[:lo] || opts[:low]).to_i
74
- hi = opts[:hi] || opts[:high] || a.length
75
-
76
- while lo < hi
77
- mid = (lo + hi) / 2
78
- v = (block_given? ? yield(a[mid]) : a[mid])
79
- if x < v
80
- hi = mid
81
- else
82
- lo = mid + 1
83
- end
84
- end
85
- return lo
86
- end
87
-
88
- alias_method :bisect, :bisect_right
89
-
90
- # Insert item x in list a, and keep it sorted assuming a is sorted.
91
- #
92
- # If x is already in a, insert it to the left of the leftmost x.
93
- #
94
- # Optional args lo (default 0) and hi (default len(a)) bound the
95
- # slice of a to be searched.
96
- #
97
- # @see http://docs.python.org/3/library/bisect.html
98
- # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
99
- # @see http://effbot.org/librarybook/bisect.htm
100
- def insort_left(a, x, opts={}, &block)
101
- return [x] if a.nil?
102
- if a.respond_to?(:dup)
103
- a = a.dup
104
- else
105
- a = collect(a)
106
- end
107
- return insort_left!(a, x, opts, &block)
108
- end
109
-
110
- # Insert item x in list a, and keep it sorted assuming a is sorted.
111
- # Returns a duplicate of the original list, leaving it intact.
112
- #
113
- # If x is already in a, insert it to the left of the leftmost x.
114
- #
115
- # Optional args lo (default 0) and hi (default len(a)) bound the
116
- # slice of a to be searched.
117
- #
118
- # @see http://docs.python.org/3/library/bisect.html
119
- # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
120
- # @see http://effbot.org/librarybook/bisect.htm
121
- def insort_left!(a, x, opts={}, &block)
122
- return [x] if a.nil?
123
- return a << x if a.empty?
124
-
125
- v = (block_given? ? yield(x) : x)
126
- index = bisect_left(a, v, opts, &block)
127
- return a.insert(index, x)
128
- end
129
-
130
- # Insert item x in list a, and keep it sorted assuming a is sorted.
131
- # Returns a duplicate of the original list, leaving it intact.
132
- #
133
- # If x is already in a, insert it to the right of the rightmost x.
134
- #
135
- # Optional args lo (default 0) and hi (default len(a)) bound the
136
- # slice of a to be searched.
137
- #
138
- # @see http://docs.python.org/3/library/bisect.html
139
- # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
140
- # @see http://effbot.org/librarybook/bisect.htm
141
- def insort_right(a, x, opts={}, &block)
142
- return [x] if a.nil?
143
- if a.respond_to?(:dup)
144
- a = a.dup
145
- else
146
- a = collect(a)
147
- end
148
- return insort_right!(a, x, opts, &block)
149
- end
150
-
151
- alias_method :insort, :insort_right
152
-
153
- # Insert item x in list a, and keep it sorted assuming a is sorted.
154
- #
155
- # If x is already in a, insert it to the right of the rightmost x.
156
- #
157
- # Optional args lo (default 0) and hi (default len(a)) bound the
158
- # slice of a to be searched.
159
- #
160
- # @see http://docs.python.org/3/library/bisect.html
161
- # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
162
- # @see http://effbot.org/librarybook/bisect.htm
163
- def insort_right!(a, x, opts={}, &block)
164
- return [x] if a.nil?
165
- return a << x if a.empty?
166
-
167
- v = (block_given? ? yield(x) : x)
168
- index = bisect_right(a, v, opts, &block)
169
- return a.insert(index, x)
170
- end
171
-
172
- alias_method :insort!, :insort_right!
173
-
174
- # Collect sample data from a generic collection, processing each item
175
- # with a block when given. Returns an array of the items from +data+
176
- # in order.
177
- #
178
- # When a block is given the block will be applied to both arguments.
179
- # Using a block in this way allows computation against a specific field
180
- # in a data set of hashes or objects.
181
- #
182
- # @yield iterates over each element in the data set
183
- # @yieldparam item each element in the data set
184
- #
185
- # @param [Enumerable] data the data set to be collected
186
- #
187
- # @return [Array] an array of zero or more items
188
- def collect(data, opts={})
189
- return [] if data.nil?
190
- sample = []
191
- data.each do |datum|
192
- datum = yield(datum) if block_given?
193
- sample << datum
194
- end
195
- return sample
196
- end
197
-
198
- # Collect sample data from a generic collection, processing each item
199
- # with a block when given. Returns an array of arrays. Each element
200
- # is a two-element array where the first element is the index within
201
- # the outer array and the second element is the corresponding item
202
- # from within +data+. The elements in the returned array are in the
203
- # same order as the original +data+ collection.
204
- #
205
- # @example
206
- # sample = [5, 1, 9, 3, 14, 9, 7]
207
- # Collection.catalog(sample) #=> [[0, 5], [1, 1], [2, 9], [3, 3], [4, 14], [5, 9], [6, 7]]
208
- #
209
- # When a block is given the block will be applied to both arguments.
210
- # Using a block in this way allows computation against a specific field
211
- # in a data set of hashes or objects.
212
- #
213
- # @yield iterates over each element in the data set
214
- # @yieldparam item each element in the data set
215
- #
216
- # @param [Enumerable] data the data set to be collected
217
- #
218
- # @return [Array] an array of zero or more items
219
- def index_and_catalog(data, opts={})
220
- return [] if data.nil?
221
- sample = []
222
- index = 0
223
- data.each do |datum|
224
- datum = yield(datum) if block_given?
225
- sample << [index, datum]
226
- index += 1
227
- end
228
- return sample
229
- end
230
-
231
- alias_method :index_and_catalogue, :index_and_catalog
232
-
233
- # Convert a hash to catalog.
234
- #
235
- # When a block is given the block will be applied to both arguments.
236
- # Using a block in this way allows computation against a specific field
237
- # in a data set of hashes or objects.
238
- #
239
- # @yield iterates over each element in the data set
240
- # @yieldparam item each element in the data set
241
- #
242
- # @param [Enumerable] data the data to convert
243
- # @param [Hash] opts search options
244
- #
245
- # @return [Array] if the data set is in ascending order
246
- def catalog_hash(data, opts={})
247
- return [] if data.nil? || data.empty?
248
- catalog = []
249
- data.each do |key, value|
250
- value = yield(value) if block_given?
251
- catalog << [key, value]
252
- end
253
- return catalog
254
- end
255
-
256
- alias_method :catalogue_hash, :catalog_hash
257
-
258
- # Convert a catalog to a hash. Keeps the last value when keys are
259
- # duplicated.
260
- #
261
- # When a block is given the block will be applied to both arguments.
262
- # Using a block in this way allows computation against a specific field
263
- # in a data set of hashes or objects.
264
- #
265
- # @yield iterates over each element in the data set
266
- # @yieldparam item each element in the data set
267
- #
268
- # @param [Enumerable] data the data to convert
269
- # @param [Hash] opts search options
270
- #
271
- # @return [Hash] if the data set is in ascending order
272
- def hash_catalog(data, opts={})
273
- return {} if data.nil? || data.empty?
274
- hash = {}
275
- data.each do |item|
276
- value = (block_given? ? yield(item.last) : item.last)
277
- hash[item.first] = value
278
- end
279
- return hash
280
- end
281
-
282
- alias_method :hash_catalogue, :hash_catalog
283
-
284
- # Scan a collection and determine if the elements are all in
285
- # ascending order. Returns true for an empty set and false for
286
- # a nil sample.
287
- #
288
- # When a block is given the block will be applied to both arguments.
289
- # Using a block in this way allows computation against a specific field
290
- # in a data set of hashes or objects.
291
- #
292
- # @yield iterates over each element in the data set
293
- # @yieldparam item each element in the data set
294
- #
295
- # @param [Enumerable] data the data set to search
296
- # @param [Hash] opts search options
297
- #
298
- # @return [true, false] if the data set is in ascending order
299
- def ascending?(data, opts={})
300
- return false if data.nil?
301
- (data.size-1).times do |i|
302
- if block_given?
303
- return false if yield(data[i]) > yield(data[i+1])
304
- else
305
- return false if data[i] > data[i+1]
306
- end
307
- end
308
- return true
309
- end
310
-
311
- # Scan a collection and determine if the elements are all in
312
- # descending order. Returns true for an empty set and false for
313
- # a nil sample.
314
- #
315
- # When a block is given the block will be applied to both arguments.
316
- # Using a block in this way allows computation against a specific field
317
- # in a data set of hashes or objects.
318
- #
319
- # @yield iterates over each element in the data set
320
- # @yieldparam item each element in the data set
321
- #
322
- # @param [Enumerable] data the data set to search
323
- # @param [Hash] opts search options
324
- #
325
- # @return [true, false] if the data set is in descending order
326
- def descending?(data, opts={})
327
- return false if data.nil?
328
- (data.size-1).times do |i|
329
- if block_given?
330
- return false if yield(data[i]) < yield(data[i+1])
331
- else
332
- return false if data[i] < data[i+1]
333
- end
334
- end
335
- return true
336
- end
337
-
338
- # Override of #slice from Ruby Array. Provides a consistent interface
339
- # to slice data structures that do not have a native #slice method.
340
- #
341
- # Returns the element at index, or returns a subarray starting at
342
- # start and continuing for length elements, or returns a subarray
343
- # specified by range. Negative indices count backward from the end
344
- # of the array (-1 is the last element). Returns nil if the index
345
- # (or starting index) is out of range.
346
- #
347
- # @overload slice(data, index)
348
- # @param [Enumerable] data the collection to slice
349
- # @param [Integer] index the index to slice
350
- #
351
- # @overload slice(data, start, length)
352
- # @param [Enumerable] data the collection to slice
353
- # @param [Integer] start the start index for the slice
354
- # @param [Integer] length the length of the slice
355
- #
356
- # @overload slice(data, range)
357
- # @param [Enumerable] data the collection to slice
358
- # @param [Range] range range of indices to include in the slice
359
- #
360
- # @return [Array] the slice
361
- def slice(data, *args)
362
- index = args[0]
363
- length = args[1]
364
- if args.size == 1
365
- if index.is_a? Range
366
- slice_with_range(data, index)
367
- else
368
- slice_with_index(data, index)
369
- end
370
- elsif args.size == 2
371
- slice_with_length(data, index, length)
372
- else
373
- raise ArgumentError.new("wrong number of arguments (#{args.size} for 2..3)")
374
- end
375
- end
376
-
377
- # :nodoc:
378
- # @private
379
- def slice_with_index(data, index)
380
- return data[index]
381
- end
382
-
383
- # :nodoc:
384
- # @private
385
- def slice_with_length(data, start, length)
386
- range = Range.new(start, start+length-1)
387
- slice_with_range(data, range)
388
- end
389
-
390
- # :nodoc:
391
- # @private
392
- def slice_with_range(data, range)
393
- return nil if range.first < 0 || range.first >= data.size
394
- last = [range.last, data.size-1].min
395
- range = Range.new(range.first, last)
396
- slice = []
397
- range.each do |index|
398
- slice << data[index]
399
- end
400
- return slice
401
- end
402
- end
403
- end
@@ -1,127 +0,0 @@
1
- module Functional
2
-
3
- module Inflect
4
- extend self
5
-
6
- # By default, +camelize+ converts strings to UpperCamelCase. If the argument
7
- # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
8
- # lowerCamelCase.
9
- #
10
- # +camelize+ will also convert '/' to '::' which is useful for converting
11
- # paths to namespaces.
12
- #
13
- # 'active_model'.camelize # => "ActiveModel"
14
- # 'active_model'.camelize(:lower) # => "activeModel"
15
- # 'active_model/errors'.camelize # => "ActiveModel::Errors"
16
- # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors"
17
- #
18
- # As a rule of thumb you can think of +camelize+ as the inverse of
19
- # +underscore+, though there are cases where that does not hold:
20
- #
21
- # 'SSLError'.underscore.camelize # => "SslError"
22
- #
23
- # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
24
- def camelize(term, uppercase_first_letter = true)
25
- string = term.to_s
26
- if uppercase_first_letter == true
27
- string = string.sub(/^[a-z\d]*/) { $&.capitalize }
28
- else
29
- string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
30
- end
31
- string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
32
- end
33
-
34
- # Makes an underscored, lowercase form from the expression in the string.
35
- #
36
- # Changes '::' to '/' to convert namespaces to paths.
37
- #
38
- # 'ActiveModel'.underscore # => "active_model"
39
- # 'ActiveModel::Errors'.underscore # => "active_model/errors"
40
- #
41
- # As a rule of thumb you can think of +underscore+ as the inverse of
42
- # +camelize+, though there are cases where that does not hold:
43
- #
44
- # 'SSLError'.underscore.camelize # => "SslError"
45
- #
46
- # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
47
- def underscore(camel_cased_word)
48
- word = camel_cased_word.to_s.dup
49
- word.gsub!('::', '/')
50
- word.gsub!(/\s+/, '_')
51
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
52
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
53
- word.tr!("-", "_")
54
- word.downcase!
55
- word
56
- end
57
-
58
- # Capitalizes the first word and turns underscores into spaces and strips a
59
- # trailing "_id", if any. Like +titleize+, this is meant for creating pretty
60
- # output.
61
- #
62
- # 'employee_salary'.humanize # => "Employee salary"
63
- # 'author_id'.humanize # => "Author"
64
- #
65
- # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
66
- def humanize(lower_case_and_underscored_word)
67
- result = lower_case_and_underscored_word.to_s.dup
68
- result.gsub!(/_id$/, "")
69
- result.tr!('_', ' ')
70
- result.gsub(/([a-z\d]*)/i) { |match| "#{match.downcase}" }.gsub(/^\w/) { $&.upcase }
71
- end
72
-
73
- # Capitalizes all the words and replaces some characters in the string to
74
- # create a nicer looking title. +titleize+ is meant for creating pretty
75
- # output. It is not used in the Rails internals.
76
- #
77
- # +titleize+ is also aliased as +titlecase+.
78
- #
79
- # 'man from the boondocks'.titleize # => "Man From The Boondocks"
80
- # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
81
- # 'TheManWithoutAPast'.titleize # => "The Man Without A Past"
82
- # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark"
83
- #
84
- # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
85
- def titleize(word)
86
- #humanize(underscore(word)).gsub(/\b(?<!['`])[a-z]/) { $&.capitalize }
87
- humanize(underscore(word)).gsub(/\b["'`]?[a-z]/) { $&.capitalize }
88
- end
89
-
90
- # Replaces underscores with dashes in the string.
91
- #
92
- # 'puni_puni'.dasherize # => "puni-puni"
93
- #
94
- # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
95
- def dasherize(underscored_word)
96
- underscored_word.to_s.dup.tr('_', '-')
97
- end
98
-
99
- # Add the given extension to the given file name. Removes the current
100
- # extension if one exists. Strips trailing characters from the file name
101
- # if present. Does nothing if the file name already has the given
102
- # extension.
103
- #
104
- # @example
105
- # extensionize('my_file.png', :png) #=> 'my_file.png'
106
- # extensionize('my_file', :png) #=> 'my_file.png'
107
- # extensionize('my_file.png', :jpg) #=> 'my_file.png.jpg'
108
- # extensionize('My File', :png) #=> 'My File.png'
109
- # extensionize('My File ', :png) #=> 'My File.png'
110
- # extensionize('my_file.png', :jpg, :chomp => true) #=> 'my_file.jpg'
111
- #
112
- # @param [String] fname the name of the file to add an extension to
113
- # @param [String, Symbol] ext the extension to append, with or without a leading dot
114
- # @param [Hash] opts processing options
115
- #
116
- # @option opts [Symbol] :chomp when true removes the existing entension
117
- # from the file name (default: false)
118
- #
119
- # @return [String] the *fname* with *ext* appended
120
- def extensionize(fname, ext, opts={})
121
- extname = File.extname(fname)
122
- return fname if (extname =~ /\.?#{ext}$/i) == 0
123
- fname = fname.gsub(/#{extname}$/, '') if opts[:chomp] == true
124
- return fname.strip + '.' + ext.to_s.gsub(/^\./, '')
125
- end
126
- end
127
- end