ksr-maybe 0.1.0

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