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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Guardfile +25 -0
- data/LICENSE.txt +22 -0
- data/README.md +124 -0
- data/Rakefile +1 -0
- data/lib/rfunc.rb +17 -0
- data/lib/rfunc/errors.rb +9 -0
- data/lib/rfunc/option.rb +334 -0
- data/lib/rfunc/seq.rb +181 -0
- data/lib/rfunc/version.rb +3 -0
- data/rfunc.gemspec +26 -0
- data/spec/rfunc/option_spec.rb +243 -0
- data/spec/rfunc/seq_spec.rb +441 -0
- data/spec/rfunc_spec.rb +29 -0
- data/spec/spec_helper.rb +19 -0
- metadata +137 -0
data/lib/rfunc/seq.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
require "rfunc/option"
|
2
|
+
|
3
|
+
module RFunc
|
4
|
+
class Seq
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :@array, :[], :to_s, :to_h, :empty?, :last, :join, :count, :size, :each, :inspect, :all?, :to_a, :to_ary
|
8
|
+
|
9
|
+
def initialize(seq=[])
|
10
|
+
raise "RFunc::Seq must be initialized with an Array. #{seq.class} is not an Array" if seq.class != Array
|
11
|
+
|
12
|
+
@array = seq
|
13
|
+
end
|
14
|
+
|
15
|
+
def all
|
16
|
+
Seq.new(@array)
|
17
|
+
end
|
18
|
+
|
19
|
+
def <<(el)
|
20
|
+
Seq.new(@array << el)
|
21
|
+
end
|
22
|
+
|
23
|
+
def +(seq2)
|
24
|
+
Seq.new(@array + seq2)
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(object)
|
28
|
+
object.class == self.class && object.members == @array
|
29
|
+
end
|
30
|
+
|
31
|
+
def <=>(seq_or_array)
|
32
|
+
if seq_or_array.is_a?(Seq)
|
33
|
+
@array <=> seq_or_array.members
|
34
|
+
else
|
35
|
+
@array <=> seq_or_array
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def members
|
40
|
+
@array
|
41
|
+
end
|
42
|
+
|
43
|
+
def head
|
44
|
+
raise "RFunc::Seq #{@array} has no head" if @array.size == 0
|
45
|
+
raw_head
|
46
|
+
end
|
47
|
+
|
48
|
+
alias_method :first, :head
|
49
|
+
|
50
|
+
def head_option
|
51
|
+
(h_e = raw_head) ? Some.new(h_e) : None.new
|
52
|
+
end
|
53
|
+
|
54
|
+
alias_method :first_option, :head_option
|
55
|
+
|
56
|
+
def tail_option
|
57
|
+
(tail[0]) ? Some.new(tail) : None.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def tail
|
61
|
+
@tail ||= (t_s = @array[1..-1]) ? Seq.new(t_s) : Seq.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def last_option
|
65
|
+
l = last
|
66
|
+
l ? Some.new(l) : None.new
|
67
|
+
end
|
68
|
+
|
69
|
+
def map(&block)
|
70
|
+
Seq.new(@array.map{|v| yield(v) })
|
71
|
+
end
|
72
|
+
|
73
|
+
def fold(accum, &block)
|
74
|
+
@array.inject(accum) {|a, el| yield(a, el) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def foldr(accum, &block)
|
78
|
+
@array.reverse.inject(accum) {|a, el| yield(a, el) }
|
79
|
+
end
|
80
|
+
|
81
|
+
alias_method :foldl, :fold
|
82
|
+
|
83
|
+
def slice(from, to)
|
84
|
+
Seq.new(@array.slice(from, to))
|
85
|
+
end
|
86
|
+
|
87
|
+
def prepend(el)
|
88
|
+
Seq.new(@array.unshift(el))
|
89
|
+
end
|
90
|
+
|
91
|
+
def append(el)
|
92
|
+
Seq.new(@array.push(el))
|
93
|
+
end
|
94
|
+
|
95
|
+
def reverse
|
96
|
+
Seq.new(@array.reverse)
|
97
|
+
end
|
98
|
+
|
99
|
+
def filter(&block)
|
100
|
+
fold(Seq.new) {|accum, el|
|
101
|
+
if yield(el)
|
102
|
+
accum.append(el)
|
103
|
+
else
|
104
|
+
accum
|
105
|
+
end
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
def find(&block)
|
110
|
+
RFunc::Option.new(@array.find {|el| yield(el) })
|
111
|
+
end
|
112
|
+
|
113
|
+
def collect(&block)
|
114
|
+
fold(Seq.new([])) {|accum, el| (res = yield(el)) ? accum.append(res) : accum }
|
115
|
+
end
|
116
|
+
|
117
|
+
def collect_first(&block)
|
118
|
+
# more performant than it's prettier version
|
119
|
+
result = nil
|
120
|
+
find {|el| result = yield(el) }
|
121
|
+
result ? RFunc::Some.new(result) : RFunc::None.new
|
122
|
+
end
|
123
|
+
|
124
|
+
def concat(seq_or_array)
|
125
|
+
if seq_or_array.is_a?(Seq)
|
126
|
+
Seq.new(@array.concat(seq_or_array.members))
|
127
|
+
else
|
128
|
+
Seq.new(@array.concat(seq_or_array))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def flat_map(&block)
|
133
|
+
fold(Seq.new) {|accum, el|
|
134
|
+
accum.concat(yield(el))
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
def flatten
|
139
|
+
Seq.new(@array.flatten)
|
140
|
+
end
|
141
|
+
|
142
|
+
alias_method :for_all?, :all?
|
143
|
+
|
144
|
+
alias_method :for_each, :each
|
145
|
+
|
146
|
+
def slice(start_index, end_index=count)
|
147
|
+
Seq.new(@array.slice(start_index, end_index) || [])
|
148
|
+
end
|
149
|
+
|
150
|
+
def intersect(seq_or_array)
|
151
|
+
if seq_or_array.is_a?(Seq)
|
152
|
+
Seq.new(@array & seq_or_array.members)
|
153
|
+
else
|
154
|
+
Seq.new(@array & seq_or_array)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def take_while(&block)
|
159
|
+
Seq.new(@array.take_while {|r| yield(r) })
|
160
|
+
end
|
161
|
+
|
162
|
+
def take(n)
|
163
|
+
Seq.new(@array.take(n))
|
164
|
+
end
|
165
|
+
|
166
|
+
def sort_by(&block)
|
167
|
+
Seq.new(@array.sort_by {|obj| yield(obj) })
|
168
|
+
end
|
169
|
+
|
170
|
+
def sort(&block)
|
171
|
+
if block_given?
|
172
|
+
Seq.new(@array.sort {|x,y| yield(x,y) })
|
173
|
+
else
|
174
|
+
Seq.new(@array.sort)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
def raw_head; @array[0] end
|
180
|
+
end
|
181
|
+
end
|
data/rfunc.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rfunc/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rfunc"
|
8
|
+
spec.version = RFunc::VERSION
|
9
|
+
spec.authors = ["Paul De Goes"]
|
10
|
+
spec.email = ["pauldegoes@hotmail.com"]
|
11
|
+
spec.summary = %q{This gem provides a more functional collection library to Ruby}
|
12
|
+
spec.description = %q{nada}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "guard-rspec"
|
25
|
+
spec.add_development_dependency "yard"
|
26
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RFunc::Option do
|
4
|
+
describe "#new" do
|
5
|
+
it "returns a Some when a value is supplied" do
|
6
|
+
RFunc::Option.new(1).should == RFunc::Some.new(1)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns a None when a value is not supplied" do
|
10
|
+
RFunc::Option.new(nil).should == RFunc::None.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#map" do
|
15
|
+
context "when Option is Some" do
|
16
|
+
let(:option) { RFunc::Option.new(1) }
|
17
|
+
it "extracts the value from an Option and returns an Option" do
|
18
|
+
option.map {|v| v.should == 1; 2 }.should == RFunc::Some.new(2)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
context "when Option is None" do
|
22
|
+
let(:option) { RFunc::Option.new(nil) }
|
23
|
+
it "doesn't extract the value and returns a None" do
|
24
|
+
option.map {|v| throw "this should not be executed" }.should == RFunc::None.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#empty?" do
|
30
|
+
context "for an Option initialized with nil" do
|
31
|
+
it "returns true" do
|
32
|
+
RFunc::Option.new(nil).empty?.should be_true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "for an None" do
|
37
|
+
it "returns true" do
|
38
|
+
RFunc::None.new.empty?.should be_true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "for an Some" do
|
43
|
+
it "returns false" do
|
44
|
+
RFunc::Some.new(1).empty?.should be_false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "for an Option initialized with a value" do
|
49
|
+
it "returns false" do
|
50
|
+
RFunc::Option.new(1).empty?.should be_false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#flat_map" do
|
56
|
+
context "when the option is a None" do
|
57
|
+
it "does not perform the operation" do
|
58
|
+
expect {
|
59
|
+
RFunc::None.new.flat_map{|v| throw 'should not throw anything' }
|
60
|
+
}.not_to raise_error
|
61
|
+
end
|
62
|
+
end
|
63
|
+
context "when the option is a Some and return type is not a Some of None" do
|
64
|
+
it "raises an error" do
|
65
|
+
expect {
|
66
|
+
RFunc::Some.new(1).flat_map{|v| v + v }
|
67
|
+
}.to raise_error(RFunc::Errors::InvalidReturnType)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
context "when the option is a Some" do
|
71
|
+
it "returns the result of the yield" do
|
72
|
+
RFunc::Some.new(1).flat_map{|v| RFunc::Some.new(v + v) }.should == RFunc::Some.new(2)
|
73
|
+
|
74
|
+
RFunc::Some.new(1).flat_map{|v| RFunc::None.new }.should == RFunc::None.new
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#or_else" do
|
79
|
+
context "when the option is a Some" do
|
80
|
+
it "returns the option" do
|
81
|
+
RFunc::Some.new(1).or_else(RFunc::Some.new(2)).get.should == 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
context "when the option is a None" do
|
85
|
+
context "when the return type of the supplied alternative is valid" do
|
86
|
+
it "returns the option" do
|
87
|
+
RFunc::None.new.or_else(RFunc::Some.new(2)).get.should == 2
|
88
|
+
end
|
89
|
+
end
|
90
|
+
context "when the return type of the supplied alternative is invalid" do
|
91
|
+
it "raises an exception" do
|
92
|
+
expect {
|
93
|
+
RFunc::None.new.or_else(2).get.should == 2
|
94
|
+
}.to raise_error(RFunc::Errors::InvalidReturnType)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "#filter" do
|
102
|
+
context "when filter matches" do
|
103
|
+
it "returns a Some" do
|
104
|
+
RFunc::Some.new(1).filter {|el| el == 1}.should == RFunc::Some.new(1)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
context "when filter does not match" do
|
108
|
+
it "returns a None" do
|
109
|
+
RFunc::Some.new(1).filter {|el| el == 2}.should == RFunc::None.new
|
110
|
+
end
|
111
|
+
end
|
112
|
+
context "when None is supplied" do
|
113
|
+
it "returns a None" do
|
114
|
+
RFunc::None.new.filter {|el| el == "foo" }.should == RFunc::None.new
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#filter_not" do
|
120
|
+
context "when filter matches" do
|
121
|
+
it "returns a None" do
|
122
|
+
RFunc::Some.new(1).filter_not {|el| el == 1}.should == RFunc::None.new
|
123
|
+
end
|
124
|
+
end
|
125
|
+
context "when filter does not match" do
|
126
|
+
it "returns a Some" do
|
127
|
+
RFunc::Some.new(1).filter_not {|el| el == 2}.should == RFunc::Some.new(1)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
context "when None is supplied" do
|
131
|
+
it "returns a None" do
|
132
|
+
RFunc::None.new.filter_not {|el| el == "foo" }.should == RFunc::None.new
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "#fold" do
|
138
|
+
context "when the option is a None" do
|
139
|
+
it "returns the alternate" do
|
140
|
+
RFunc::None.new.fold(2) {|el|
|
141
|
+
el * el
|
142
|
+
}.should == 2
|
143
|
+
end
|
144
|
+
end
|
145
|
+
context "when the option is a Some" do
|
146
|
+
it "returns the alternate" do
|
147
|
+
RFunc::Some.new(10).fold(2) {|el|
|
148
|
+
el * el
|
149
|
+
}.should == 100
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "#flatten" do
|
155
|
+
context "when the option is a None" do
|
156
|
+
it "returns a None" do
|
157
|
+
RFunc::None.new.flatten.should == RFunc::None.new
|
158
|
+
end
|
159
|
+
end
|
160
|
+
context "when the option is a Some[Some]" do
|
161
|
+
it "returns a flattened Some" do
|
162
|
+
RFunc::Some.new(RFunc::Some.new(1)).flatten.should == RFunc::Some.new(1)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
context "when the option is a Some[None]" do
|
166
|
+
it "returns a None" do
|
167
|
+
RFunc::Some.new(RFunc::None.new).flatten.should == RFunc::None.new
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "#collect" do
|
173
|
+
context "when the option is a None" do
|
174
|
+
it "returns a None" do
|
175
|
+
RFunc::None.new.collect {|el|
|
176
|
+
case el > 2
|
177
|
+
when true; el * 100
|
178
|
+
else nil
|
179
|
+
end
|
180
|
+
}.should be_a(RFunc::None)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
context "when the option is a Some with on which the provided block returns nil" do
|
184
|
+
it "returns a None" do
|
185
|
+
RFunc::Some.new(2).collect {|el|
|
186
|
+
case el > 2
|
187
|
+
when true; el * 100
|
188
|
+
else nil
|
189
|
+
end
|
190
|
+
}.should be_a(RFunc::None)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
context "when the option is a Some with on which the provided block returns a value" do
|
194
|
+
it "returns a None" do
|
195
|
+
RFunc::Some.new(3).collect {|el|
|
196
|
+
case el > 2
|
197
|
+
when true; el * 100
|
198
|
+
else 100
|
199
|
+
end
|
200
|
+
}.should == RFunc::Some.new(300)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe "#for_all" do
|
206
|
+
context "when the option is a None" do
|
207
|
+
it "returns true" do
|
208
|
+
RFunc::None.new.for_all {|el| el == 2}.should == true
|
209
|
+
end
|
210
|
+
end
|
211
|
+
context "when the option is a Some and the result of the provided block is true" do
|
212
|
+
it "returns true" do
|
213
|
+
RFunc::Some.new(2).for_all {|el| el == 2}.should == true
|
214
|
+
end
|
215
|
+
end
|
216
|
+
context "when the option is a Some and the result of the provided block is false" do
|
217
|
+
it "returns false" do
|
218
|
+
RFunc::Some.new(1).for_all {|el| el == 2}.should == false
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "#for_each" do
|
224
|
+
context "when the option is a None" do
|
225
|
+
it "does not execute the yield" do
|
226
|
+
count = 0
|
227
|
+
|
228
|
+
RFunc::None.new.for_each {|el| count = 10 }
|
229
|
+
|
230
|
+
count.should == 0
|
231
|
+
end
|
232
|
+
end
|
233
|
+
context "when the option is a Some" do
|
234
|
+
it "not execute the yield with the Option value" do
|
235
|
+
count = 0
|
236
|
+
|
237
|
+
RFunc::Some.new(10).for_each {|el| count = el }
|
238
|
+
|
239
|
+
count.should == 10
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|