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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/LICENSE +13 -0
- data/NOTICE.md +25 -0
- data/README.md +16 -0
- data/Rakefile +5 -0
- data/circle.yml +10 -0
- data/ksr-maybe.gemspec +16 -0
- data/lib/maybe.rb +418 -0
- data/test/maybe_test.rb +249 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/Gemfile
ADDED
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.
|
data/NOTICE.md
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
data/circle.yml
ADDED
data/ksr-maybe.gemspec
ADDED
@@ -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
|
data/lib/maybe.rb
ADDED
@@ -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
|
data/test/maybe_test.rb
ADDED
@@ -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: []
|