ksr-maybe 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b9b624308416ed8b74db359022fe3cbc65c63e5b
4
+ data.tar.gz: d03ab54204740c3c608e4bc5faea88952cb373de
5
+ SHA512:
6
+ metadata.gz: 67d3f7414267af4083a86ad199fbabf76eefcd5414edb62345141b9c848c686bdc59f4e0bce496e13b6aacb8e4097c095fe895f26bf9e44bb3f7dfab73155651
7
+ data.tar.gz: dcb5d360e0475ffdcdbd7c222b1f25e23d2e5f1f3b73fa9a4747511cdb72a543752fc292cf721f37a2694f765776163a7d45cc7b1c9f4106ec76dfe290ac26c0
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright Kickstarter, PBC.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,25 @@
1
+ # contracts.ruby (dependency)
2
+
3
+ Copyright (c) 2012-2016 Aditya Bhargava
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,16 @@
1
+ # ksr-maybe
2
+
3
+ This gem provides a `Maybe` type. The `Maybe` type either contains a value
4
+ (represented as `Just`) or it is empty (represented as `Nothing`).
5
+
6
+
7
+ ## Contracts
8
+
9
+ This gem utilizes the ['contracts.ruby' gem][contracts], which adds dynamic type-checking to the 'ruby-maybe' codebase. By default, the 'contracts.ruby' gem will `raise` an error during runtime upon a type mismatch. To override this error throwing behavior, see [this 'contracts.ruby' documentation][contracts-override].
10
+
11
+ ## Licenses
12
+
13
+ See [LICENSES.md](./LICENSES.md).
14
+
15
+ [contracts]: http://egonschiele.github.io/contracts.ruby/
16
+ [contracts-override]: https://github.com/egonSchiele/contracts.ruby/blob/master/TUTORIAL.md#failure-callbacks
@@ -0,0 +1,5 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = "test/**/*_test.rb"
5
+ end
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ jobs:
3
+ build:
4
+ working_directory: ~/ksr-maybe
5
+ docker:
6
+ - image: ruby:2.2
7
+ steps:
8
+ - checkout
9
+ - run: bundle install
10
+ - run: bundle exec rake test
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'ksr-maybe'
3
+ s.version = '0.1.0'
4
+ s.summary = 'A library providing the optional type \'Maybe\''
5
+ s.authors = ['Ryan Closner', 'Corey Farwell']
6
+ s.email = 'eng@kickstarter.com'
7
+ s.files = `git ls-files`.split("\n")
8
+ s.homepage = 'http://github.com/kickstarter/ruby-maybe'
9
+ s.license = 'Apache-2.0'
10
+
11
+ s.add_dependency 'contracts', '~> 0.16.0'
12
+
13
+ s.add_development_dependency 'shoulda-context', '~> 1.2'
14
+ s.add_development_dependency 'minitest', '~> 5.10'
15
+ s.add_development_dependency 'rake', '~> 12.0'
16
+ end
@@ -0,0 +1,418 @@
1
+ require "contracts"
2
+
3
+ class MaybeOf < Contracts::CallableClass
4
+ def initialize(*values)
5
+ @values = values
6
+ end
7
+
8
+ def valid?(obj)
9
+ obj.is_a?(Nothing) || obj.is_a?(Just) && @values.any? {|v| Contract.valid?(obj.get, v) }
10
+ end
11
+ end
12
+
13
+ # NOTE: Nothing and Just need to be initialized ahead of time so they're available
14
+ # for Contract definitions below.
15
+
16
+ Nothing = Class.new
17
+ Just = Class.new
18
+
19
+ # Maybe is a union type that is expressed in terms of Nothing and Just. It is
20
+ # useful for gracefully handling potentially null values:
21
+ #
22
+ # @example when value is non-null
23
+ # User.create!(email: "john@doe.com", name: "John Doe")
24
+ #
25
+ # Maybe
26
+ # .from_nullable(User.find_by(email: "john@doe.com"))
27
+ # .map {|user| user.name }
28
+ # .get_or_else { "NO NAME" }
29
+ #
30
+ # #=> "John Doe"
31
+ #
32
+ # @example when value is null
33
+ # Maybe
34
+ # .from_nullable(User.find_by(email: "not@present.com"))
35
+ # .map {|user| user.name }
36
+ # .get_or_else { "NO NAME" }
37
+ #
38
+ # #=> "NO NAME"
39
+ #
40
+ module Maybe
41
+ include Contracts::Core
42
+ C = Contracts
43
+ private_constant :C
44
+
45
+ # An alias for instantiating a Nothing object
46
+ #
47
+ # @example
48
+ # Maybe.Nothing
49
+ # #=> Nothing
50
+ #
51
+ # @returns [Nothing] an instance of Nothing
52
+ Contract C::None => Nothing
53
+ def self.Nothing
54
+ Nothing.instance
55
+ end
56
+
57
+ # An alias for instantiating a Just object
58
+ #
59
+ # @example
60
+ # Maybe.Just(1)
61
+ # #=> Just(1)
62
+ #
63
+ # @param value [Any] the value to wrap
64
+ # @returns [Just<Any>] an instance of Just wrapping some object
65
+ Contract C::Any => Just
66
+ def self.Just(value)
67
+ Just.new(value)
68
+ end
69
+
70
+ # An alias for instantiating a Just object
71
+ #
72
+ # @example
73
+ # Maybe.of(1)
74
+ # #=> Just(1)
75
+ #
76
+ # @param value [Any] the value to wrap
77
+ # @returns [Just<Any>] an instance of Just wrapping the value
78
+ Contract C::Any => Just
79
+ def self.of(value)
80
+ Just.new(value)
81
+ end
82
+
83
+ # Takes a nullable object and returns either an instance of Just wrapping
84
+ # the object in the case that the object is non-null, or an instance of
85
+ # Nothing in the case that the object is null.
86
+ #
87
+ # @example with a non-null value
88
+ # User.create(email: "john@doe.com")
89
+ #
90
+ # Maybe.from_nullable(User.find_by(email: "john@doe.com"))
91
+ # #=> Just(1)
92
+ #
93
+ # @example with a null value
94
+ # Maybe.from_nullable(User.find_by(email: "not@present.com"))
95
+ # #=> Nothing
96
+ #
97
+ # @param value [Any] the value to wrap
98
+ # @returns [Just<Any>,Nothing] either an instance of Just wrapping the value,
99
+ # or an instance of Nothing
100
+ Contract C::Any => Maybe
101
+ def self.from_nullable(value)
102
+ value.nil? ? Nothing.instance : Just.new(value)
103
+ end
104
+
105
+ # Takes a set of Maybes, and attempts to combine their values, and wrap them
106
+ # into a single Maybe.
107
+ #
108
+ # @example with a single instance of Just
109
+ # Maybe.zip(Maybe.Just(1))
110
+ # #=> Just(1)
111
+ #
112
+ # @example with a single instance of Nothing
113
+ # Maybe.zip(Maybe.Nothing)
114
+ # #=> Nothing
115
+ #
116
+ # @example with multiple instances of Just
117
+ # Maybe.zip(Maybe.Just(1), Maybe.Just(2))
118
+ # #=> Just([1,2])
119
+ #
120
+ # @example with multiple instances of Nothing
121
+ # Maybe.zip(Maybe.Nothing, Maybe.Nothing)
122
+ # #=> Nothing
123
+ #
124
+ # @example a mixture of Just and Nothing instances
125
+ # Maybe.zip(Maybe.Just(1), Maybe.Nothing, Maybe.Just(2))
126
+ # #=> Nothing
127
+ #
128
+ # @param m [Array<Just<Any>>, Array<Nothing>] a collection of Maybes
129
+ # @return [Just<Any>,Nothing] either a combined Just, or Nothing
130
+ Contract C::Args[Maybe] => C::Or[Maybe]
131
+ def self.zip(fst, snd, *rest)
132
+ [fst, snd, *rest].reduce(of([])) do |accum, maybe|
133
+ accum.flat_map do |accum_|
134
+ maybe.map {|maybe_| accum_ + [maybe_] }
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ # Nothing is a member of the Maybe union type that represents a null value.
141
+ # It's used in conjunction with the Just type to allow one to gracefully handle
142
+ # null values without having to create a large amount of conditional logic.
143
+ class Nothing
144
+ include Maybe
145
+ include Contracts::Core
146
+ C = Contracts
147
+ private_constant :C
148
+
149
+ NothingError = Class.new(RuntimeError)
150
+
151
+ def self.instance
152
+ @instance ||= new
153
+ end
154
+ private_class_method :new
155
+
156
+ # Attempts to return the value wrapped by the Maybe type. In the case of a
157
+ # Nothing, however, it will raise due to the fact that Nothing cannot hold a
158
+ # value.
159
+ #
160
+ # @example
161
+ # Maybe.Nothing.get
162
+ # #=> Nothing::NothingError: cannot get the value of Nothing.
163
+ #
164
+ # @raises [Nothing::NothingError] an error raised when one attempts to fetch the value
165
+ Contract C::None => C::None
166
+ def get
167
+ raise NothingError, "cannot get the value of Nothing."
168
+ end
169
+
170
+ # Attempts to either return the value wrapped by the Maybe type (in the case
171
+ # of a Just), or provide an alternative value when the instance is a Nothing.
172
+ #
173
+ # @example
174
+ # Maybe.Nothing.get_or_else { "NO NAME" }
175
+ # #=> "NO NAME"
176
+ #
177
+ # @yield [] the alternative value when called on an instance of Nothing
178
+ # @return [Any] either the value contained by the Maybe or the value
179
+ # returned by the block passed.
180
+ Contract C::Func[C::None => C::Any] => C::Any
181
+ def get_or_else(&f)
182
+ f.call
183
+ end
184
+
185
+ # A convenience method for determining whether a Maybe type is an instance
186
+ # of Just.
187
+ # @return [Boolean] whether or not the value is a Just
188
+ Contract C::None => C::Bool
189
+ def just?
190
+ false
191
+ end
192
+
193
+ # A convenience method for determining whether a Maybe type is an instance
194
+ # of Nothing.
195
+ # @return [Boolean] whether or not the value is a Nothing
196
+ Contract C::None => C::Bool
197
+ def nothing?
198
+ true
199
+ end
200
+
201
+ # Transforms a Maybe type into a Maybe of the same type. When called on an
202
+ # instance of Just it will apply the block with its value as an argument, and
203
+ # re-wrap the result of the block in another Just. When called on an instance
204
+ # of Nothing, however, the method will no-op and simply return itself.
205
+ #
206
+ # @example
207
+ # Maybe.Nothing.map {|num| num + 1 }
208
+ # #=> Nothing
209
+ #
210
+ # @yield [value] the block to apply
211
+ # @yieldparam [Any] the value wrapped by the Maybe type
212
+ # @yieldreturn [Any] the value returned by the block
213
+ # @return [Nothing] the instance of Nothing
214
+ Contract C::Func[C::Any => C::Any] => Nothing
215
+ def map(&f)
216
+ self
217
+ end
218
+
219
+ # Transforms a Maybe type into another Maybe (not necessarily of the same type).
220
+ # When called on an instance of Just it will will apply the block with its value
221
+ # as the argument (it differs from Maybe#map in that it is the responsibility
222
+ # of the caller to rewrap the result in a Maybe type). When called on an instance
223
+ # of Nothing the method will no-op and simply return itself.
224
+ #
225
+ # @example
226
+ # Maybe.Nothing.flat_map {|num| num == 0 ? Maybe.Just(num) : Maybe.Nothing }
227
+ # #=> Nothing
228
+ #
229
+ # @yield [value] the block to apply
230
+ # @yieldparam [Any] the value wrapped by the Maybe type
231
+ # @yieldreturn [Nothing, Just<Any>] the Maybe returned by the block
232
+ # @return [Nothing] the Maybe returned by the block
233
+ Contract C::Func[C::Any => Maybe] => Nothing
234
+ def flat_map(&f)
235
+ self
236
+ end
237
+
238
+ # Applies the function inside a Maybe type to another applicative type.
239
+ #
240
+ # @example
241
+ # Maybe.Nothing.ap( Maybe.of(->(str){ str.upcase }) )
242
+ # #=> Nothing
243
+ #
244
+ # @param m [Just<Proc>, Nothing] an instance of the Maybe type to apply
245
+ # @return [Nothing] the result of applying the function wrapped in a Maybe
246
+ Contract MaybeOf[Proc] => Nothing
247
+ def ap(m)
248
+ self
249
+ end
250
+ alias apply ap
251
+
252
+ # An equality operator
253
+ #
254
+ # @example
255
+ # Maybe.Nothing == Maybe.Nothing
256
+ # #=> true
257
+ #
258
+ # @param m [Any] the object to compare against
259
+ # @return [Boolean] whether or not the object is equal
260
+ Contract C::Any => C::Bool
261
+ def ==(m)
262
+ m.is_a?(Nothing)
263
+ end
264
+
265
+ # Overrides the default print behavior
266
+ #
267
+ # @example
268
+ # puts(Maybe.Nothing)
269
+ # #=> "Nothing"
270
+ #
271
+ # @return [String] the String to print
272
+ Contract C::None => String
273
+ def inspect
274
+ "Nothing"
275
+ end
276
+ end
277
+
278
+ # Just is a member of the Maybe union type that represents a non-null value.
279
+ # It's used in conjunction with the Nothing type to allow one to gracefully
280
+ # handle null values without having to create a large amount of conditional
281
+ # logic.
282
+ class Just
283
+ include Maybe
284
+ include Contracts::Core
285
+ C = Contracts
286
+ private_constant :C
287
+
288
+ def initialize(value)
289
+ @value = value
290
+ end
291
+
292
+ # Attempts to return the value wrapped by the Maybe type. In the case of a
293
+ # Nothing, however, it will raise due to the fact that Nothing cannot hold a
294
+ # value.
295
+ #
296
+ # @example
297
+ # Maybe.Just(1).get
298
+ # #=> 1
299
+ #
300
+ # @return [Any] the value wrapped by the Maybe object
301
+ Contract C::None => C::Any
302
+ def get
303
+ @value
304
+ end
305
+
306
+ # Attempts to either return the value wrapped by the Maybe type (in the case
307
+ # of a Just), or provide an alternative value when the instance is a Nothing.
308
+ #
309
+ # @example
310
+ # Maybe.Just("John Doe").get_or_else { "NO NAME" }
311
+ # #=> "John Doe"
312
+ #
313
+ # @yield [] the alternative value when called on an instance of Nothing
314
+ # @return [Any] the value wrapped by the Maybe object
315
+ Contract C::Func[C::None => C::Any] => C::Any
316
+ def get_or_else(&f)
317
+ @value
318
+ end
319
+
320
+ # A convenience method for determining whether a Maybe type is an instance
321
+ # of Just.
322
+ # @return [Boolean] whether or not the value is a Just
323
+ Contract C::None => C::Bool
324
+ def just?
325
+ true
326
+ end
327
+
328
+ # A convenience method for determining whether a Maybe type is an instance
329
+ # of Nothing.
330
+ # @return [Boolean] whether or not the value is a Nothing
331
+ Contract C::None => C::Bool
332
+ def nothing?
333
+ false
334
+ end
335
+
336
+ # Transforms a Maybe type into a Maybe of the same type. When called on an
337
+ # instance of Just it will apply the block with its value as an argument, and
338
+ # re-wrap the result of the block in another Just. When called on an instance
339
+ # of Nothing, however, the method will no-op and simply return itself.
340
+ #
341
+ # @example
342
+ # Maybe.Just(1).map {|num| num + 1 }
343
+ # #=> Just(2)
344
+ #
345
+ # @yield [value] the block to apply
346
+ # @yieldparam [Any] the value wrapped by the Maybe type
347
+ # @yieldreturn [Any] the value returned by the block
348
+ # @return [Just<Any>] the value returned by the block and wrapped by a Maybe
349
+ Contract C::Func[C::Any => C::Any] => Just
350
+ def map(&f)
351
+ flat_map {|value| Maybe.of(f.call(value)) }
352
+ end
353
+
354
+ # Transforms a Maybe type into another Maybe (not necessarily of the same type).
355
+ # When called on an instance of Just it will will apply the block with its value
356
+ # as the argument (it differs from Maybe#map in that it is the responsibility
357
+ # of the caller to rewrap the result in a Maybe type). When called on an instance
358
+ # of Nothing the method will no-op and simply return itself.
359
+ #
360
+ # @example
361
+ # User.create!(email: "john@doe.com", slug: "johndoe")
362
+ # Maybe
363
+ # .from_nullable(User.find_by(email: "john@doe.com"))
364
+ # .flat_map {|u| u.slug == "johndoe" ? Maybe.Just(u) : Maybe.Nothing }
365
+ #
366
+ # #=> Just(#<User email: "john@doe.com", slug: "johndoe">)
367
+ #
368
+ # @yield [value] the block to apply
369
+ # @yieldparam [Any] the value wrapped by the Maybe type
370
+ # @yieldreturn [Nothing, Just<Any>] the Maybe returned by the block
371
+ # @return [Nothing, Just<Any>] the Maybe returned by the block
372
+ Contract C::Func[C::Any => Maybe] => Maybe
373
+ def flat_map(&f)
374
+ f.call(@value)
375
+ end
376
+
377
+ # Applies the function inside a Maybe type to another applicative type.
378
+ #
379
+ # @example
380
+ # Maybe.of("hello, world!").ap( Maybe.of(->(str){ str.upcase }) )
381
+ # #=> Just("HELLO, WORLD!")
382
+ #
383
+ # @param m [Just<Proc>, Nothing] an instance of the Maybe type to apply
384
+ # @return [Just<Any>, Nothing] the result of applying the function wrapped in a Maybe
385
+ Contract MaybeOf[Proc] => Maybe
386
+ def ap(m)
387
+ m.flat_map {|f| map(&f) }
388
+ end
389
+ alias apply ap
390
+
391
+ # An equality operator
392
+ #
393
+ # @example
394
+ # Maybe.Just(1) == Maybe.Just(1)
395
+ # #=> true
396
+ #
397
+ # Maybe.Just(0) == Maybe.Just(1)
398
+ # #=> false
399
+ #
400
+ # @param m [Any] the object to compare against
401
+ # @return [Boolean] whether or not the object is equal
402
+ Contract C::Any => C::Bool
403
+ def ==(m)
404
+ m.is_a?(Just) && m.get == get
405
+ end
406
+
407
+ # Overrides the default print behavior
408
+ #
409
+ # @example
410
+ # puts(Maybe.Just(1))
411
+ # #=> "Just(1)"
412
+ #
413
+ # @return [String] the String to print
414
+ Contract C::None => String
415
+ def inspect
416
+ "Just(#{@value.inspect})"
417
+ end
418
+ end
@@ -0,0 +1,249 @@
1
+ require 'minitest/autorun'
2
+ require 'shoulda/context'
3
+
4
+ require_relative "../lib/maybe"
5
+
6
+ class MaybeTest < Minitest::Test
7
+ context ".Nothing" do
8
+ should "return an instance of Nothing" do
9
+ assert_equal Nothing.instance, Maybe.Nothing
10
+ end
11
+ end
12
+
13
+ context ".Just" do
14
+ should "return an instance of Just" do
15
+ assert_equal Maybe.of(1), Maybe.Just(1)
16
+ end
17
+ end
18
+
19
+ context ".of" do
20
+ should "return an instance of Just" do
21
+ assert_equal Just.new(1), Maybe.of(1)
22
+ end
23
+ end
24
+
25
+ context ".from_nullable" do
26
+ context "when value is `nil`" do
27
+ should "return an instance of Nothing" do
28
+ assert_equal Nothing.instance, Maybe.from_nullable(nil)
29
+ end
30
+ end
31
+
32
+ context "when value is not `nil`" do
33
+ should "return an instance of Just" do
34
+ assert_equal Maybe.of(1), Maybe.from_nullable(1)
35
+ end
36
+ end
37
+ end
38
+
39
+ context ".zip" do
40
+ setup do
41
+ @just = Maybe.of(1)
42
+ @nothing = Nothing.instance
43
+ end
44
+
45
+ context "with only Just values" do
46
+ should "return an instance of Just" do
47
+ assert_equal Maybe.of([1,1]), Maybe.zip(@just, @just)
48
+ end
49
+ end
50
+
51
+ context "with only Nothing values" do
52
+ should "return an instance of Nothing" do
53
+ assert_equal Nothing.instance, Maybe.zip(@nothing, @nothing)
54
+ end
55
+ end
56
+
57
+ context "with a mixture of Just and Nothing values" do
58
+ should "return an instance of Nothing" do
59
+ assert_equal Nothing.instance, Maybe.zip(@just, @nothing, @just)
60
+ end
61
+ end
62
+ end
63
+
64
+ context "Nothing" do
65
+ subject do
66
+ Nothing.instance
67
+ end
68
+
69
+ context "#get" do
70
+ should "raise an error" do
71
+ assert_raises(Nothing::NothingError) { subject.get }
72
+ end
73
+ end
74
+
75
+ context "#get_or_else" do
76
+ should "call the block" do
77
+ assert_equal 1, subject.get_or_else { 1 }
78
+ end
79
+ end
80
+
81
+ context "#just?" do
82
+ should "return false" do
83
+ refute subject.just?
84
+ end
85
+ end
86
+
87
+ context "#nothing?" do
88
+ should "return true" do
89
+ assert subject.nothing?
90
+ end
91
+ end
92
+
93
+ context "#map" do
94
+ should "return self" do
95
+ assert_equal subject, subject.map {|_n| 1 }
96
+ end
97
+ end
98
+
99
+ context "#flat_map" do
100
+ should "return self" do
101
+ assert_equal subject, subject.flat_map {|_n| 1 }
102
+ end
103
+ end
104
+
105
+ context "#ap" do
106
+ should "return self" do
107
+ assert_equal subject, subject.ap(Maybe.of(->(x){ x + 1 }))
108
+ end
109
+ end
110
+
111
+ context "#==" do
112
+ context "when object is a Nothing" do
113
+ should "return true" do
114
+ assert_equal subject, Nothing.instance
115
+ end
116
+ end
117
+
118
+ context "when object is a Just" do
119
+ should "return false" do
120
+ refute_equal subject, Maybe.of(1)
121
+ end
122
+ end
123
+ end
124
+
125
+ context "#inspect" do
126
+ should "return the correct String" do
127
+ assert_equal "Nothing", subject.inspect
128
+ end
129
+ end
130
+ end
131
+
132
+ context "Just" do
133
+ subject do
134
+ Maybe.of(1)
135
+ end
136
+
137
+ context "#get" do
138
+ should "return the value" do
139
+ assert_equal 1, subject.get
140
+ end
141
+ end
142
+
143
+ context "#get_or_else" do
144
+ should "return the value" do
145
+ assert_equal 1, subject.get_or_else { 2 }
146
+ end
147
+ end
148
+
149
+ context "#just?" do
150
+ should "return true" do
151
+ assert subject.just?
152
+ end
153
+ end
154
+
155
+ context "#nothing?" do
156
+ should "return false" do
157
+ refute subject.nothing?
158
+ end
159
+ end
160
+
161
+ context "#map" do
162
+ setup do
163
+ @f = ->(n){ n + 1 }
164
+ @g = ->(n){ n * 2 }
165
+ end
166
+
167
+ should "hold to the law of identity" do
168
+ assert_equal subject, subject.map {|x| x }
169
+ end
170
+
171
+ should "hold to the law of composition" do
172
+ assert_equal subject.map(&@g).map(&@f), subject.map {|n| @f.call(@g.call(n)) }
173
+ end
174
+ end
175
+
176
+ context "#flat_map" do
177
+ setup do
178
+ @f = ->(n) { Maybe.of(n + 1) }
179
+ @g = ->(n) { Maybe.of(n * 2) }
180
+ end
181
+
182
+ should "fail if the function does not rewrap the value" do
183
+ assert_raises(ReturnContractError) { subject.flat_map {|x| x } }
184
+ end
185
+
186
+ should "be equivalent to calling the function with the held value" do
187
+ assert_equal @f.call(subject.get), subject.flat_map(&@f)
188
+ end
189
+
190
+ should "be equivalent when the function merely rewraps" do
191
+ assert_equal subject, subject.flat_map(&Maybe.method(:of))
192
+ end
193
+
194
+ should "hold to the law of associativity" do
195
+ assert_equal subject.flat_map(&@f).flat_map(&@g), subject.flat_map {|v| @f.call(v).flat_map(&@g) }
196
+ end
197
+ end
198
+
199
+ context "#ap" do
200
+ setup do
201
+ @identity = ->(x){ x }
202
+ @upcase = ->(str){ str.upcase }
203
+ @add = ->(n){ n + 1 }
204
+ end
205
+
206
+ should "raise if argument is not an instance of Maybe" do
207
+ assert_raises(ContractError) { Maybe.of(1).ap(->(x){ x + 1 }) }
208
+ end
209
+
210
+ should "raise if argument is not a Maybe holding a function" do
211
+ assert_raises(ContractError) { Maybe.of(1).ap(Maybe.of(1)) }
212
+ end
213
+
214
+ should "work when given a Maybe holding a function" do
215
+ assert_equal Maybe.of("HELLO, WORLD!"), Maybe.of("hello, world!").ap(Maybe.of(@upcase))
216
+ end
217
+
218
+ should "hold to the law of identity" do
219
+ assert_equal Maybe.of(1), Maybe.of(1).ap(Maybe.of(@identity))
220
+ end
221
+ end
222
+
223
+ context "#==" do
224
+ context "when object is a Just that is equivalent" do
225
+ should "return true" do
226
+ assert_equal Maybe.of(1), Maybe.of(1)
227
+ end
228
+ end
229
+
230
+ context "when object is a Just that is equivalent" do
231
+ should "return false" do
232
+ refute_equal Maybe.of(1), Maybe.of(2)
233
+ end
234
+ end
235
+
236
+ context "when object is a Nothing" do
237
+ should "return false" do
238
+ refute_equal Nothing.instance, Maybe.of(1)
239
+ end
240
+ end
241
+ end
242
+
243
+ context "#inspect" do
244
+ should "return the correct String" do
245
+ assert_equal "Just(1)", Maybe.of(1).inspect
246
+ end
247
+ end
248
+ end
249
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ksr-maybe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Closner
8
+ - Corey Farwell
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-06-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: contracts
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: 0.16.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: 0.16.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: shoulda-context
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '1.2'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '1.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: minitest
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '5.10'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '5.10'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '12.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '12.0'
70
+ description:
71
+ email: eng@kickstarter.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - Gemfile
78
+ - LICENSE
79
+ - NOTICE.md
80
+ - README.md
81
+ - Rakefile
82
+ - circle.yml
83
+ - ksr-maybe.gemspec
84
+ - lib/maybe.rb
85
+ - test/maybe_test.rb
86
+ homepage: http://github.com/kickstarter/ruby-maybe
87
+ licenses:
88
+ - Apache-2.0
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.0.14.1
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: A library providing the optional type 'Maybe'
110
+ test_files: []