data-option 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/.rubocop.yml +58 -0
- data/LICENSE.txt +18 -0
- data/README.md +291 -0
- data/Rakefile +16 -0
- data/Steepfile +7 -0
- data/examples/try_division.rb +32 -0
- data/lib/data/option/ext/kernel.rb +7 -0
- data/lib/data/option/none.rb +55 -0
- data/lib/data/option/refinement.rb +15 -0
- data/lib/data/option/some.rb +70 -0
- data/lib/data/option/version.rb +7 -0
- data/lib/data/option.rb +17 -0
- data/sig/data/option.rbs +81 -0
- metadata +56 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c482aa6857657bf2abcfbd51ef9bf19f485c2096645301a61a1b3391573f0bd4
|
4
|
+
data.tar.gz: 950af8e9f4b3fceec8c1dad184597a821e3e6094961bf352d31730eb627250ce
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 972535d4f3b0c310ef247a680c6cd47b7e7041285b1a8a46d3af46f91022e4c42e9d3773ef814478db32ae6e0d4c4bc43a1f64283eb7a4e3b6ba76ba9f647a87
|
7
|
+
data.tar.gz: 93fb7a298300733845dbfec34099bfde13dd6f084cceccd281fad62fc204438f903c25a1868171e3db0836503125f133e0b989ea0fa17d95af736f894f606397
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
plugins:
|
2
|
+
- rubocop-minitest
|
3
|
+
- rubocop-rake
|
4
|
+
|
5
|
+
AllCops:
|
6
|
+
TargetRubyVersion: 3.4
|
7
|
+
NewCops: enable
|
8
|
+
|
9
|
+
Bundler/GemFilename:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Layout/SpaceInsideHashLiteralBraces:
|
13
|
+
EnforcedStyle: no_space
|
14
|
+
|
15
|
+
Lint/AmbiguousOperatorPrecedence:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Lint/BinaryOperatorWithIdenticalOperands:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Lint/SuppressedException:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Metrics/AbcSize:
|
25
|
+
Max: 25
|
26
|
+
|
27
|
+
Metrics/ClassLength:
|
28
|
+
Max: 200
|
29
|
+
|
30
|
+
Metrics/MethodLength:
|
31
|
+
Max: 42
|
32
|
+
|
33
|
+
Minitest/MultipleAssertions:
|
34
|
+
Max: 6
|
35
|
+
|
36
|
+
Naming/FileName:
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
Naming/MethodName:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
Naming/RescuedExceptionsVariableName:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
Style/Alias:
|
46
|
+
Enabled: false
|
47
|
+
|
48
|
+
Style/Documentation:
|
49
|
+
Enabled: false
|
50
|
+
|
51
|
+
Style/MixinUsage:
|
52
|
+
Enabled: false
|
53
|
+
|
54
|
+
Style/NumberedParametersLimit:
|
55
|
+
Max: 9
|
56
|
+
|
57
|
+
Style/RescueStandardError:
|
58
|
+
Enabled: false
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) Shannon Skipper
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
# Data::Option
|
2
|
+
|
3
|
+
This gem provides a `None` and `Some` with methods for creating, matching, and transforming optional values with Rust-like semantics.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```bash
|
8
|
+
bundle add data-option
|
9
|
+
```
|
10
|
+
|
11
|
+
If you're not using Bundler, install the gem with:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
gem install data-option
|
15
|
+
```
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
### Basic Example
|
20
|
+
|
21
|
+
To use `Some[value]` and `None[]`, first `include Data::Option`.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'data/option'
|
25
|
+
|
26
|
+
include Data::Option
|
27
|
+
|
28
|
+
def checked_division(dividend, divisor)
|
29
|
+
if divisor.zero?
|
30
|
+
None[]
|
31
|
+
else
|
32
|
+
Some[dividend / divisor]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def try_division(dividend, divisor)
|
37
|
+
case checked_division(dividend, divisor)
|
38
|
+
in None
|
39
|
+
puts "#{dividend} / #{divisor} failed!"
|
40
|
+
in Some(quotient)
|
41
|
+
puts "#{dividend} / #{divisor} = #{quotient}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
try_division(4, 2)
|
46
|
+
# >> 4 / 2 = 2
|
47
|
+
try_division(1, 0)
|
48
|
+
# >> 1 / 0 failed!
|
49
|
+
```
|
50
|
+
|
51
|
+
### Creating an Option Value
|
52
|
+
|
53
|
+
A `Some` or `None` can be created with multiple syntaxes:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
require 'data/option'
|
57
|
+
|
58
|
+
include Data::Option
|
59
|
+
|
60
|
+
Some[42] # => Some[42]
|
61
|
+
None[] # => None
|
62
|
+
|
63
|
+
Some.new(42) # => Some[42]
|
64
|
+
None.instance # => None
|
65
|
+
```
|
66
|
+
|
67
|
+
Kernel#Option creates an Option from any value:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
require 'data/option'
|
71
|
+
|
72
|
+
Option(42) # => Some[42]
|
73
|
+
Option(false) # => Some[false]
|
74
|
+
Option(nil) # => None
|
75
|
+
```
|
76
|
+
|
77
|
+
The equivalent class method `Option.from`:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
require 'data/option'
|
81
|
+
|
82
|
+
# Same behavior as the Kernel method
|
83
|
+
Option.from(42) # => Some[42]
|
84
|
+
Option.from(false) # => Some[false]
|
85
|
+
Option.from(nil) # => None
|
86
|
+
```
|
87
|
+
|
88
|
+
Enable Object#to_option via refinements:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
require 'data/option'
|
92
|
+
|
93
|
+
using Data::Option::Refinement
|
94
|
+
|
95
|
+
42.to_option # => Some[42]
|
96
|
+
false.to_option # => Some[false]
|
97
|
+
nil.to_option # => None
|
98
|
+
|
99
|
+
42.to_option.map(&:succ) # => Some[43]
|
100
|
+
nil.to_option.map(&:succ) # => None
|
101
|
+
```
|
102
|
+
|
103
|
+
### Predicates
|
104
|
+
|
105
|
+
Check the state of your Option values with predicate methods:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
require 'data/option'
|
109
|
+
|
110
|
+
include Data::Option
|
111
|
+
|
112
|
+
some = Some[42]
|
113
|
+
none = None[]
|
114
|
+
|
115
|
+
some.some? # => true
|
116
|
+
some.none? # => false
|
117
|
+
none.some? # => false
|
118
|
+
none.none? # => true
|
119
|
+
|
120
|
+
Some[42].some_and?(&:even?) # => true (value exists and is even)
|
121
|
+
Some[41].some_and?(&:even?) # => false (value exists but isn't even)
|
122
|
+
None[].some_and?(&:even?) # => false (no value to check)
|
123
|
+
|
124
|
+
Some[42].none_or?(&:even?) # => true (either None or predicate is true)
|
125
|
+
Some[41].none_or?(&:even?) # => false (has value but predicate is false)
|
126
|
+
None[].none_or? { false } # => true (None satisfies this predicate)
|
127
|
+
```
|
128
|
+
|
129
|
+
### Unwrapping
|
130
|
+
|
131
|
+
Extract values from an Option safely:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
require 'data/option'
|
135
|
+
|
136
|
+
include Data::Option
|
137
|
+
|
138
|
+
some = Some[42]
|
139
|
+
none = None[]
|
140
|
+
|
141
|
+
some.unwrap # => 42
|
142
|
+
none.unwrap #!> None::UnwrapError
|
143
|
+
|
144
|
+
some.unwrap_or(0) # => 42
|
145
|
+
none.unwrap_or(0) # => 0
|
146
|
+
|
147
|
+
some.unwrap_or_else { rand(100) } # => 42
|
148
|
+
none.unwrap_or_else { rand(100) } # => some random number
|
149
|
+
|
150
|
+
some.expect('Should contain value') # => 42
|
151
|
+
none.expect('Missing expected value') #!> None::UnwrapError
|
152
|
+
```
|
153
|
+
|
154
|
+
### Enumeration and Transformation
|
155
|
+
|
156
|
+
Map, filter, and transform Option values:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
require 'data/option'
|
160
|
+
|
161
|
+
include Data::Option
|
162
|
+
|
163
|
+
Some[42].each { |value| puts value } # prints 42
|
164
|
+
None[].each { |value| puts value } # no output
|
165
|
+
|
166
|
+
Some[42].map { |n| n + 1 } # => Some[43]
|
167
|
+
None[].map { |n| n + 1 } # => None
|
168
|
+
|
169
|
+
Some[42].filter(&:even?) # => Some[42]
|
170
|
+
Some[41].filter(&:even?) # => None
|
171
|
+
None[].filter { true } # => None
|
172
|
+
|
173
|
+
Some[42].flat_map { |n| Some[n + 1] } # => Some[43]
|
174
|
+
Some[42].flat_map { |_| None[] } # => None
|
175
|
+
None[].flat_map { |n| Some[n + 1] } # => None
|
176
|
+
|
177
|
+
Some[Some[42]].flatten # => Some[42]
|
178
|
+
Some[None[]].flatten # => None
|
179
|
+
Some[42].flatten # => Some[42]
|
180
|
+
None[].flatten # => None
|
181
|
+
|
182
|
+
Some[42].map_or(0) { |n| n + 1 } # => 43
|
183
|
+
None[].map_or(0) { |n| n + 1 } # => 0
|
184
|
+
|
185
|
+
Some[42].map_or_else(-> { rand(100) }) { |n| n + 1 } # => 43
|
186
|
+
None[].map_or_else(-> { rand(100) }) { |n| n + 1 } # => random number
|
187
|
+
```
|
188
|
+
|
189
|
+
### Logical Operations
|
190
|
+
|
191
|
+
Combine Option values with logical operations:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
require 'data/option'
|
195
|
+
|
196
|
+
include Data::Option
|
197
|
+
|
198
|
+
Some[1].and(Some[2]) # => Some[2]
|
199
|
+
Some[1].and(None[]) # => None
|
200
|
+
None[].and(Some[1]) # => None
|
201
|
+
None[].and(None[]) # => None
|
202
|
+
|
203
|
+
Some[1].and_then { |n| Some[n + 1] } # => Some[2]
|
204
|
+
Some[1].and_then { |_| None[] } # => None
|
205
|
+
None[].and_then { |n| Some[n + 1] } # => None
|
206
|
+
|
207
|
+
Some[1].or(Some[2]) # => Some[1]
|
208
|
+
Some[1].or(None[]) # => Some[1]
|
209
|
+
None[].or(Some[2]) # => Some[2]
|
210
|
+
None[].or(None[]) # => None
|
211
|
+
|
212
|
+
Some[1].or_else { Some[2] } # => Some[1]
|
213
|
+
None[].or_else { Some[2] } # => Some[2]
|
214
|
+
None[].or_else { None[] } # => None
|
215
|
+
|
216
|
+
Some[1].xor(Some[2]) # => None
|
217
|
+
Some[1].xor(None[]) # => Some[1]
|
218
|
+
None[].xor(Some[2]) # => Some[2]
|
219
|
+
None[].xor(None[]) # => None
|
220
|
+
```
|
221
|
+
|
222
|
+
### String Representation and Formatting
|
223
|
+
|
224
|
+
Options provide clear string representations:
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
require 'data/option'
|
228
|
+
|
229
|
+
include Data::Option
|
230
|
+
|
231
|
+
Some[42].to_s # => "Some[42]"
|
232
|
+
Some[42].inspect # => "Some[42]"
|
233
|
+
None[].to_s # => "None"
|
234
|
+
None[].inspect # => "None"
|
235
|
+
|
236
|
+
Some[[1, 2, 3]].inspect # => "Some[[1, 2, 3]]"
|
237
|
+
Some[Some["hello"]].inspect # => "Some[Some[\"hello\"]]"
|
238
|
+
|
239
|
+
require 'pp'
|
240
|
+
PP.pp(Some[42]) # Some[ 42 ]
|
241
|
+
PP.pp(Some[Some[None[]]]) # Some[ Some[ None ] ]
|
242
|
+
```
|
243
|
+
|
244
|
+
### Comparison and Sorting
|
245
|
+
|
246
|
+
`Some` and `None` are comparable and sortable. `None` is considered lower than all `Some` values, and `Some` values are ordered by their contained values:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
require 'data/option'
|
250
|
+
|
251
|
+
include Data::Option
|
252
|
+
|
253
|
+
Some[1] < Some[2] # => true
|
254
|
+
Some[2] > Some[1] # => true
|
255
|
+
Some[1] == Some[1] # => true
|
256
|
+
|
257
|
+
Some[1] > None[] # => true
|
258
|
+
None[] < Some[1] # => true
|
259
|
+
|
260
|
+
[Some[3], None[], Some[1], Some[2]].sort # => [None[], Some[1], Some[2], Some[3]]
|
261
|
+
```
|
262
|
+
|
263
|
+
### Method Chaining
|
264
|
+
|
265
|
+
Chain operations for elegant functional transformations:
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
require 'data/option'
|
269
|
+
|
270
|
+
include Data::Option
|
271
|
+
|
272
|
+
Option(42)
|
273
|
+
.map { |it| it * 10 } # => Some[420]
|
274
|
+
.flat_map { |it| Option(it - 440) } # => Some[-20]
|
275
|
+
.filter(&:positive?) # => None
|
276
|
+
.map { |it| it * 10 } # => None
|
277
|
+
.unwrap_or(99) # => 99
|
278
|
+
```
|
279
|
+
|
280
|
+
### Method Overview
|
281
|
+
|
282
|
+
`Some` and `None` both respond to a wide range of utility methods that cover common functional programming patterns:
|
283
|
+
|
284
|
+
- Checking: `some?`, `none?`, `some_and?`, `none_or?`
|
285
|
+
- Unwrapping: `unwrap`, `unwrap_or`, `unwrap_or_else`, `expect`
|
286
|
+
- Transforming: `map`, `flat_map`, `filter`, `flatten`, `map_or`, `map_or_else`, `each`
|
287
|
+
- Combining: `and`, `and_then`, `or`, `or_else`, `xor`
|
288
|
+
- Formatting: `to_s`, `inspect`
|
289
|
+
- Comparison: `<=>`, `==`, `<`, `>`, etc.
|
290
|
+
|
291
|
+
See Rust's Option [Method overview](https://doc.rust-lang.org/std/option/#method-overview) for background and inspiration for these methods.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rubocop/rake_task'
|
6
|
+
|
7
|
+
task default: %i[test rubocop]
|
8
|
+
|
9
|
+
Rake::TestTask.new do |test|
|
10
|
+
test.pattern = 'test/**/*_test.rb'
|
11
|
+
test.warning = false
|
12
|
+
end
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new do |task|
|
15
|
+
task.plugins << 'rubocop-minitest'
|
16
|
+
end
|
data/Steepfile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'data/option'
|
4
|
+
|
5
|
+
include Data::Option
|
6
|
+
|
7
|
+
# An integer division that doesn't `raise ZeroDivisionError`
|
8
|
+
def checked_division(dividend, divisor)
|
9
|
+
if divisor.zero?
|
10
|
+
# Failure is represented as the `None` variant
|
11
|
+
None[]
|
12
|
+
else
|
13
|
+
# Result is wrapped in a `Some` variant
|
14
|
+
Some[dividend / divisor]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# This function handles a division that may not succeed
|
19
|
+
def try_division(dividend, divisor)
|
20
|
+
# `Option` values can be pattern matched, just like other enums
|
21
|
+
case checked_division(dividend, divisor)
|
22
|
+
in None
|
23
|
+
puts "#{dividend} / #{divisor} failed!"
|
24
|
+
in Some(quotient)
|
25
|
+
puts "#{dividend} / #{divisor} = #{quotient}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
try_division(4, 2)
|
30
|
+
# >> 4 / 2 = 2
|
31
|
+
try_division(1, 0)
|
32
|
+
# >> 1 / 0 failed!
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
class Data
|
6
|
+
module Option
|
7
|
+
None = Data.define
|
8
|
+
class None
|
9
|
+
class UnwrapError < StandardError
|
10
|
+
def initialize(message = 'cannot unwrap a `None` value') = super
|
11
|
+
end
|
12
|
+
|
13
|
+
include Comparable
|
14
|
+
include Singleton
|
15
|
+
|
16
|
+
def <=>(other) = other.is_a?(Some) ? -1 : 0
|
17
|
+
|
18
|
+
class << self
|
19
|
+
alias [] instance
|
20
|
+
end
|
21
|
+
|
22
|
+
def unwrap = raise UnwrapError
|
23
|
+
def unwrap_or_else = yield
|
24
|
+
|
25
|
+
def unwrap_or(default) = default
|
26
|
+
def expect(message) = raise UnwrapError, message
|
27
|
+
|
28
|
+
def and(_other) = self
|
29
|
+
def and_then = self
|
30
|
+
def or(other) = other.is_a?(Some) ? other : self
|
31
|
+
alias or_else unwrap_or_else
|
32
|
+
alias xor or
|
33
|
+
|
34
|
+
def each = self
|
35
|
+
alias map each
|
36
|
+
alias filter each
|
37
|
+
alias flat_map each
|
38
|
+
alias flatten each
|
39
|
+
|
40
|
+
alias map_or unwrap_or
|
41
|
+
def map_or_else(default) = default.call
|
42
|
+
|
43
|
+
def some? = false
|
44
|
+
def none? = true
|
45
|
+
|
46
|
+
alias some_and? some?
|
47
|
+
alias none_or? none?
|
48
|
+
|
49
|
+
def inspect = 'None'
|
50
|
+
alias to_s inspect
|
51
|
+
|
52
|
+
def pretty_print(pp) = pp.text(inspect)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Data
|
4
|
+
module Option
|
5
|
+
Some = Data.define(:value) do
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
def <=>(other)
|
9
|
+
return 1 if other.is_a?(None)
|
10
|
+
|
11
|
+
value <=> other.value
|
12
|
+
end
|
13
|
+
|
14
|
+
alias unwrap value
|
15
|
+
alias unwrap_or_else value
|
16
|
+
|
17
|
+
def unwrap_or(_default) = value
|
18
|
+
def expect(_message) = value
|
19
|
+
|
20
|
+
def and(other) = other.is_a?(Some) ? other : None[]
|
21
|
+
def and_then = yield value
|
22
|
+
def or(_other) = self
|
23
|
+
def or_else = self
|
24
|
+
def xor(other) = other.is_a?(Some) ? None[] : self
|
25
|
+
|
26
|
+
def each
|
27
|
+
yield value
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def map = Some[yield value]
|
33
|
+
def filter = yield(value) ? self : None[]
|
34
|
+
def flat_map(&) = map(&).flatten
|
35
|
+
|
36
|
+
def flatten
|
37
|
+
case value
|
38
|
+
in None | Some
|
39
|
+
value
|
40
|
+
else
|
41
|
+
self
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def map_or(_default) = yield value
|
46
|
+
alias map_or_else map_or
|
47
|
+
|
48
|
+
def some? = true
|
49
|
+
def none? = false
|
50
|
+
|
51
|
+
def some_and? = yield value
|
52
|
+
alias none_or? some_and?
|
53
|
+
|
54
|
+
def inspect = "Some[#{value.inspect}]"
|
55
|
+
alias to_s inspect
|
56
|
+
|
57
|
+
def pretty_print(pp)
|
58
|
+
pp.group(1, 'Some[', ']') do
|
59
|
+
pp.breakable('')
|
60
|
+
case value
|
61
|
+
in Some | None
|
62
|
+
value.pretty_print(pp)
|
63
|
+
else
|
64
|
+
pp.pp(value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/data/option.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'option/ext/kernel'
|
4
|
+
require_relative 'option/none'
|
5
|
+
require_relative 'option/refinement'
|
6
|
+
require_relative 'option/some'
|
7
|
+
require_relative 'option/version'
|
8
|
+
|
9
|
+
class Data
|
10
|
+
module Option
|
11
|
+
# Creates an Option from a value.
|
12
|
+
# Returns `None` if the value is `nil`, otherwise returns `Some[value]`.
|
13
|
+
def self.from(value)
|
14
|
+
value.nil? ? None[] : Some[value]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/sig/data/option.rbs
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
type option[T] = Data::Option::Some[T] | Data::Option::None
|
2
|
+
|
3
|
+
class Data
|
4
|
+
module Option
|
5
|
+
VERSION: String
|
6
|
+
def self.from: [T] (T?) -> option[T]
|
7
|
+
|
8
|
+
class Some[T]
|
9
|
+
attr_reader value: T
|
10
|
+
def self.[]: [T] (T) -> Some[T]
|
11
|
+
def initialize: (T) -> void
|
12
|
+
def <=>: (None) -> 1
|
13
|
+
| (Some[untyped]) -> (1 | 0 | -1 | nil)
|
14
|
+
def unwrap: -> T
|
15
|
+
def unwrap_or: (untyped) -> T
|
16
|
+
def unwrap_or_else: -> T
|
17
|
+
def expect: (untyped) -> T
|
18
|
+
def and: (untyped) -> option[untyped]
|
19
|
+
def and_then: { (T) -> option[untyped] } -> option[untyped]
|
20
|
+
def or: (untyped) -> Some[T]
|
21
|
+
def or_else: -> Some[T]
|
22
|
+
def xor: (untyped) -> option[untyped]
|
23
|
+
def each: { (T) -> void } -> Some[T]
|
24
|
+
def map: { (T) -> untyped } -> Some[untyped]
|
25
|
+
def filter: { (T) -> boolish } -> option[T]
|
26
|
+
def flat_map: { (T) -> untyped } -> untyped
|
27
|
+
def flatten: -> untyped
|
28
|
+
def map_or: (untyped) { (T) -> untyped } -> untyped
|
29
|
+
def map_or_else: (untyped) { (T) -> untyped } -> untyped
|
30
|
+
def some?: -> true
|
31
|
+
def none?: -> false
|
32
|
+
def some_and?: { (T) -> boolish } -> boolish
|
33
|
+
def none_or?: { (T) -> boolish } -> boolish
|
34
|
+
def to_s: -> String
|
35
|
+
def inspect: -> String
|
36
|
+
def pretty_print: (PP) -> void
|
37
|
+
end
|
38
|
+
|
39
|
+
class None
|
40
|
+
class UnwrapError < StandardError
|
41
|
+
def initialize: (?String) -> void
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.[]: -> None
|
45
|
+
def initialize: () -> void
|
46
|
+
def <=>: (Some[untyped]) -> -1
|
47
|
+
| (None) -> 0
|
48
|
+
def unwrap: -> untyped
|
49
|
+
def unwrap_or_else: { () -> untyped } -> untyped
|
50
|
+
def unwrap_or: (untyped) -> untyped
|
51
|
+
def expect: (untyped) -> untyped
|
52
|
+
def and: (untyped) -> None
|
53
|
+
def and_then: -> None
|
54
|
+
def or: (untyped) -> untyped
|
55
|
+
def or_else: { () -> untyped } -> untyped
|
56
|
+
def xor: (untyped) -> untyped
|
57
|
+
def each: { (untyped) -> void } -> None
|
58
|
+
def map: { (untyped) -> untyped } -> None
|
59
|
+
def filter: { (untyped) -> untyped } -> None
|
60
|
+
def flat_map: { (untyped) -> untyped } -> None
|
61
|
+
def flatten: -> None
|
62
|
+
def map_or: (untyped) { (untyped) -> untyped } -> untyped
|
63
|
+
def map_or_else: (untyped) -> untyped
|
64
|
+
def some?: -> false
|
65
|
+
def none?: -> true
|
66
|
+
def some_and?: { (untyped) -> untyped } -> false
|
67
|
+
def none_or?: { (untyped) -> untyped } -> true
|
68
|
+
def to_s: -> String
|
69
|
+
def inspect: -> String
|
70
|
+
def pretty_print: (PP) -> void
|
71
|
+
end
|
72
|
+
|
73
|
+
module Refinement
|
74
|
+
def to_option: () -> option[self]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
module Kernel
|
80
|
+
def Option: (untyped) -> option[untyped]
|
81
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: data-option
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shannon Skipper
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: Some and None Option classes with Rust-like semantics
|
13
|
+
email:
|
14
|
+
- shannonskipper@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- ".rubocop.yml"
|
20
|
+
- LICENSE.txt
|
21
|
+
- README.md
|
22
|
+
- Rakefile
|
23
|
+
- Steepfile
|
24
|
+
- examples/try_division.rb
|
25
|
+
- lib/data/option.rb
|
26
|
+
- lib/data/option/ext/kernel.rb
|
27
|
+
- lib/data/option/none.rb
|
28
|
+
- lib/data/option/refinement.rb
|
29
|
+
- lib/data/option/some.rb
|
30
|
+
- lib/data/option/version.rb
|
31
|
+
- sig/data/option.rbs
|
32
|
+
homepage: https://github.com/havenwood/data-option
|
33
|
+
licenses:
|
34
|
+
- MIT
|
35
|
+
metadata:
|
36
|
+
homepage_uri: https://github.com/havenwood/data-option
|
37
|
+
source_code_uri: https://github.com/havenwood/data-option
|
38
|
+
rubygems_mfa_required: 'true'
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 3.4.0
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubygems_version: 3.7.0.dev
|
54
|
+
specification_version: 4
|
55
|
+
summary: Option classes for Some and None
|
56
|
+
test_files: []
|