rfunc 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5ada99d6bda2552bc00c7b03da343402a02c8eb6
4
+ data.tar.gz: c5ca325b37d69b6ce07f5f559d0d6689c1c91f5a
5
+ SHA512:
6
+ metadata.gz: 5f791b958bc7def9f8657c9e200037298f190f5d4886f7755813450b487e3521948ead4be2620bacefc64af27fb7508e1750c472e338819f7a18faeca0343528
7
+ data.tar.gz: 3ee8bca64120fb8ea66c742e3bd12859ce069784ff6c3967dcad97984e74f32f57cb94d7d9a0a161bc2635fe1a2a6a9ef5d42878a3de9a1e43095a2798e5f4a8
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ rfunc
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rfunc.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,25 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, :cmd => 'bundle exec rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
8
+ watch('spec/spec_helper.rb') { "spec" }
9
+
10
+ # Rails example
11
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
12
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
13
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
14
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
15
+ watch('config/routes.rb') { "spec/routing" }
16
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
17
+
18
+ # Capybara features specs
19
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
20
+
21
+ # Turnip features and steps
22
+ watch(%r{^spec/acceptance/(.+)\.feature$})
23
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
24
+ end
25
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Paul De Goes
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # RFunc
2
+
3
+ This gem provides access to a functionally oriented collections library. First release will include Seq and Option support.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rfunc'
10
+
11
+ And then execute:
12
+
13
+ $ bundle install
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rfunc
18
+
19
+ ## Usage
20
+
21
+ ###RFunc
22
+ The RFunc module provides access to some helper methods that let you form RFunc classes in a less verbosity.
23
+
24
+ require "rfunc"
25
+
26
+ include RFunc
27
+
28
+ Seq([1,2,3]) => RFunc::Seq(1,2,3)
29
+
30
+ Option(nil) => RFunc::None
31
+
32
+ Option(1) => RFunc::Some(1)
33
+
34
+
35
+ ####RFunc::Seq
36
+ The RFunc::Seq (Sequence) is a replacement for the Ruby Array class which it accepts and provides the following methods:
37
+
38
+ * [] => access an element of the Seq by index
39
+
40
+ RFunc::Seq.new([1])[0] => 1
41
+
42
+ * head => return the first element of the Seq
43
+
44
+ RFunc::Seq.new([1]).head => 1
45
+
46
+ * head_option => return an Option of the first element of the Seq
47
+
48
+ RFunc::Seq.new([1]).head_option => Some(1)
49
+
50
+ * tail => return all elements of the Seq except the head
51
+
52
+ RFunc::Seq.new([1,2,3]).tail => Seq([2,3])
53
+
54
+ * tail_option => return an Option of all the elements of the Seq
55
+
56
+ RFunc::Seq.new([1,2,3]).tail_option => Some(Seq([2,3]))
57
+
58
+ * map(block) => returns a Seq, the members of which have been operated on by the provided block
59
+
60
+ RFunc::Seq.new([1,2,3]).map{|v| v*2 } => Seq([2, 4, 6])
61
+
62
+ * slice(from, to) => returns a Seq containing the given range
63
+
64
+ RFunc::Seq.new([1,2,3,4,5]).slice(2,3) => Seq([2,3,4])
65
+
66
+ * fold(accum, &block) => returns the value of the yielded block, which takes an accumulator and a Seq element from left to right
67
+
68
+ RFunc::Seq.new([1,2,3]).fold(RFunc::Seq.new([])) {|accum, el| accum.append(el + el) } => Seq([2,4,6])
69
+
70
+ * foldr(accum, &block) => returns the value of the yielded block, which takes an accumulator and a Seq element from right to left
71
+
72
+ RFunc::Seq.new([1,2,3]).foldr(RFunc::Seq.new([])) {|accum, el| accum.append(el + el) } => Seq([6,4,2])
73
+
74
+ * first_option => returns an Option of the first element (Some of None)
75
+
76
+ RFunc::Seq.new([1,2]).first_option => Some(1)
77
+ RFunc::Seq.new([]).first_option => None
78
+
79
+ * last_option => returns an Option of the last element (Some of None)
80
+
81
+ RFunc::Seq.new([1,2]).last_option => Some(2)
82
+ RFunc::Seq.new([]).last_option => None
83
+
84
+ * append(el) => returns an RFunc::Seq with the element appended
85
+
86
+ RFunc::Seq.new([1]).append(2) => Seq([1,2])
87
+
88
+ * prepend(el) => returns an RFunc::Seq with the element prepended
89
+
90
+ RFunc::Seq.new([2]).prepend(1) => Seq([1,2])
91
+
92
+ * empty? => returns true or false based on whether or not there are elements in the Seq
93
+
94
+ RFunc::Seq.new([]).empty? => true
95
+ RFunc::Seq.new([1]).empty? => false
96
+
97
+ * reverse => returns an RFunc::Seq with the elements reversed
98
+
99
+ RFunc::Seq.new([1,2,3,4,5]).reverse => Seq([5,4,3,2,1])
100
+
101
+ * concat => returns an RFunc::Seq with the provided elements appended
102
+ RFunc::Seq.new([1,2,3]).concat([2,1]) => Seq([1, 2, 3, 2, 1])
103
+ RFunc::Seq.new([1,2,3]).concat(RFunc::Seq.new([2,1])) => Seq([1, 2, 3, 2, 1])
104
+
105
+ * flat_map => returns a flattened RFunc::Seq, whose members are derived from operating on each element of the existing Seq
106
+ RFunc::Seq.new([1,2,3]).flat_map {|el| [el, el -1, el -2] } => Seq([1, 0, -1, 2, 1, 0, 3, 2, 1]>)
107
+
108
+ ## CAVEATES
109
+
110
+ Ruby is NOT a functional language, nor is it optimized for functional code (tail call recursion for instance). As a result, this library utilizes native Ruby comprehensions in order to remain performant and useful. Any libraries built on this code should take the same approach.
111
+
112
+ ## TODO
113
+
114
+ * Complete Seq library
115
+ * Complete Option library
116
+ * head
117
+
118
+ ## Contributing
119
+
120
+ 1. Fork it ( http://github.com/<my-github-username>/rfunc/fork )
121
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
122
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
123
+ 4. Push to the branch (`git push origin my-new-feature`)
124
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/rfunc.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "rfunc/version"
2
+ require "rfunc/option"
3
+ require "rfunc/seq"
4
+ require "rfunc/errors"
5
+
6
+
7
+ module RFunc
8
+ module_function
9
+
10
+ def Seq(a); RFunc::Seq.new(a) end
11
+
12
+ def Option(v); RFunc::Option.new(v) end
13
+
14
+ def Some(v); RFunc::Some.new(v) end
15
+
16
+ def None; RFunc::None.new end
17
+ end
@@ -0,0 +1,9 @@
1
+ module RFunc
2
+ module Errors
3
+ class InvalidReturnType < ArgumentError
4
+ def initialize(o,expected_type)
5
+ super("Invalid Return type for #{o.class}. Expected #{expected_type}")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,334 @@
1
+ require 'rfunc/errors'
2
+
3
+ module RFunc
4
+ class AbstractOption
5
+ # Initializes the class
6
+ #
7
+ # @param value [Any] the initial value of the Option
8
+ #
9
+ # @return [AbstractOption] and instance of the class
10
+ #
11
+ def initialize(value)
12
+ @value = value
13
+ end
14
+
15
+ # Comparator that allows one Option to be compared against another
16
+ # by value
17
+ #
18
+ # @return [Boolean] true if the Option values are identical and false
19
+ # if not
20
+ #
21
+ def ==(object)
22
+ object.class == self.class && object.get == @value
23
+ end
24
+
25
+ # Extracts the value of the option
26
+ #
27
+ # @return [Any] the value of the option
28
+ def get; @value end
29
+
30
+ # Filters the Some by a given function
31
+ #
32
+ # @param block [Function] the function which will operate on the
33
+ # option's value if present (should return Bool) and
34
+ # be used to determine if a Some of None will be
35
+ # returned
36
+ # @return [RFunc::Option] the Option result of the filter
37
+ #
38
+ def filter(&block)
39
+ map {|el| yield(el)}.get_or_else { false } ? self : None.new
40
+ end
41
+
42
+ # Filters the Some by the inverse of a given function
43
+ #
44
+ # @param block [Function] the function which will operate on the
45
+ # option's value if present (should return Bool) and
46
+ # be used to determine if a Some of None will be
47
+ # returned
48
+ # @return [RFunc::Option] the Option result of the filter
49
+ #
50
+ def filter_not(&block)
51
+ map {|el| !yield(el) }.get_or_else { false } ? self : None.new
52
+ end
53
+
54
+ alias_method :find, :filter
55
+
56
+ # Tests whether or not an object is an Option
57
+ #
58
+ # @param el [Any] the object to test
59
+ #
60
+ # @return [Boolean] true if the object is an Option or false if not
61
+ #
62
+ def is_option?(el)
63
+ el.is_a?(AbstractOption)
64
+ end
65
+
66
+ private
67
+
68
+ def validated_option_type(r)
69
+ raise RFunc::Errors::InvalidReturnType.new(r, AbstractOption) if !is_option?(r)
70
+ r
71
+ end
72
+ end
73
+
74
+ class Some < AbstractOption
75
+ def initialize(something)
76
+ throw "RFunc::Some cannot be initialized with a nil" if something.nil?
77
+ super(something)
78
+ end
79
+
80
+ # Maintains signiture parity with None.get_or_else
81
+ #
82
+ # @param block [Function] the value that would return if the current
83
+ # Option was a None
84
+ #
85
+ # @return [Any] Option value
86
+ #
87
+ def get_or_else(&block)
88
+ get
89
+ end
90
+
91
+ # Maintains signiture parity with None.or_else
92
+ #
93
+ # @param v [Option] the option that would return if the present Option
94
+ # were a None
95
+ #
96
+ # @return [Option] the current option
97
+ #
98
+ def or_else(v)
99
+ self
100
+ end
101
+
102
+ # Operates on the Option value
103
+ #
104
+ # @param block [Function] the function which will be used to operate
105
+ # on the Option's value
106
+ #
107
+ # @return [Option] the result of the function's operation
108
+ # as an Option
109
+ #
110
+ def map(&block)
111
+ Option.new(yield(@value))
112
+ end
113
+
114
+ # Indicates if the Option has a value
115
+ #
116
+ # @return [Boolean] false
117
+ #
118
+ def empty?
119
+ false
120
+ end
121
+
122
+ # Applies a function to the value of the Option and
123
+ # flattens the resulting Option[Option] into a single Option
124
+ #
125
+ # @param block [Function] the function which will operate on the
126
+ # Option's value (should return an Option)
127
+ #
128
+ # @return [RFunc::Option] the Option result of flattening the current
129
+ # provided function's option
130
+ #
131
+ def flat_map(&block)
132
+ validated_option_type(yield(get))
133
+ end
134
+
135
+ # Operates on the value of the Option
136
+ #
137
+ # @param alternate [Any] the value to return if the Option is a None
138
+ # @param block [Function] the function which will operate on the
139
+ # current Option's value
140
+ #
141
+ # @return [Any] the result of applying the supplied block to the current
142
+ # Option value
143
+ #
144
+ def fold(alternate, &block)
145
+ yield(get)
146
+ end
147
+
148
+ # Flattens nested Options into a single option
149
+ #
150
+ # @return [Option] a Some if the contained value is a Some or a None
151
+ # if not
152
+ #
153
+ def flatten
154
+ is_option?(@value) ? @value : self
155
+ end
156
+
157
+ # Operates on the value of the Option if it exists and meets a criteria
158
+ #
159
+ # @param block [Function] the block that can be used to modify the value
160
+ # (should be a case statement with nil return for non operation)
161
+ #
162
+ # @return [Option] the resulting Option containing a value modified by
163
+ # the supplied block
164
+ #
165
+ def collect(&block)
166
+ result = yield(@value)
167
+ result.nil? ? None.new : Some.new(result)
168
+ end
169
+
170
+ # Counts the number of elements for which the block returns true
171
+ #
172
+ # @param block [Function] the function that determines whether the
173
+ # value should be counted
174
+ #
175
+ # @return [Int] 1 if the block returned true for this Option value or 0 if not
176
+ #
177
+ def count(&block)
178
+ yield(value) == true ? 1 : 0
179
+ end
180
+
181
+ # Determines if the current Option value satisfies the supplied block
182
+ #
183
+ # @param block [Function] the function that determines whether the
184
+ # value satisfies an expectation
185
+ #
186
+ # @return [Boolean] true if the block returns true for the value or false if not
187
+ #
188
+ def for_all(&block)
189
+ yield(@value)
190
+ end
191
+
192
+ # Executes the provided block with the current Option value
193
+ #
194
+ # @param block [Function] the function that takes the current Option's value
195
+ #
196
+ # @return [Nil] nil
197
+ #
198
+ def for_each(&block)
199
+ yield(@value)
200
+ nil
201
+ end
202
+ end
203
+
204
+ class None < AbstractOption
205
+ def initialize
206
+ super(nil)
207
+ end
208
+
209
+ # Allows for the return of a replacement value
210
+ #
211
+ # @param block [Function] a block returning an alternate value
212
+ #
213
+ # @return [Any] the result of the supplied block
214
+ #
215
+ def get_or_else(&block)
216
+ yield
217
+ end
218
+
219
+ # Returns an alternative Option for the None
220
+ #
221
+ # @param v [Option] the option to be returned
222
+ #
223
+ # @return [Option] the provided alternate Option
224
+ #
225
+ def or_else(v)
226
+ validated_option_type(v)
227
+ end
228
+
229
+ # Maintains signiture parity with Some.map
230
+ #
231
+ # @param block [Function] the function which would be used to operate
232
+ # on the Option's value
233
+ #
234
+ # @return [None] a None
235
+ #
236
+ def map(&block)
237
+ self
238
+ end
239
+
240
+ # Indicates if the Option has a value
241
+ #
242
+ # @return [Boolean] true
243
+ #
244
+ def empty?
245
+ true
246
+ end
247
+
248
+ # Maintains signiture parity with Some.flat_map
249
+ #
250
+ # @param block [Function] the function which will operate on the
251
+ # Option's value (should return an Option)
252
+ #
253
+ # @return [RFunc::Option] a None
254
+ #
255
+ def flat_map(&block)
256
+ self
257
+ end
258
+
259
+ # Maintains signiture parity with Some.fold
260
+ #
261
+ # @param alternate [Any] the value to return if the Option is a None
262
+ # @param block [Function] the function which will operate on the
263
+ # current Option's value
264
+ #
265
+ # @return [Any] the alternate
266
+ #
267
+ def fold(alternate, &block)
268
+ alternate
269
+ end
270
+
271
+ # Maintains signiture parity with Some.flatten
272
+ #
273
+ # @return [None] self
274
+ #
275
+ def flatten
276
+ self
277
+ end
278
+
279
+
280
+ # Maintains signiture parity with Some.collect
281
+ #
282
+ # @param block [Function] the block that can be used to modify the value
283
+ # (should be a case statement with nil return for non operation)
284
+ #
285
+ # @return [None] self
286
+ #
287
+ def collect(&block)
288
+ self
289
+ end
290
+
291
+ # Counts the number of elements for which the block returns true
292
+ #
293
+ # @param block [Function] the function that determines whether the
294
+ # value should be counted
295
+ #
296
+ # @return [Int] 0 (there is no value to operate on)
297
+ #
298
+ def count(&block)
299
+ 0
300
+ end
301
+
302
+ # Determines if the current Option value satisfies the supplied block
303
+ #
304
+ # @param block [Function] the function that determines whether the
305
+ # value satisfies an expectation
306
+ #
307
+ # @return [Boolean] true because the is no value to violate the supplied block
308
+ #
309
+ def for_all(&block)
310
+ true
311
+ end
312
+
313
+ # Returns nil (no value to execute on)
314
+ #
315
+ # @param block [Function] the function that takes the current Option's value
316
+ #
317
+ # @return [Nil] nil
318
+ #
319
+ def for_each(&block)
320
+ nil
321
+ end
322
+ end
323
+
324
+ module Option
325
+ def self.new(something_or_nothing)
326
+ case something_or_nothing.nil?
327
+ when false
328
+ Some.new(something_or_nothing)
329
+ else
330
+ None.new
331
+ end
332
+ end
333
+ end
334
+ end