failure 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 +50 -0
- data/Gemfile +6 -0
- data/README.md +217 -0
- data/Rakefile +12 -0
- data/failure.gemspec +22 -0
- data/lib/either.rb +76 -0
- data/lib/failure.rb +2 -0
- data/lib/maybe.rb +79 -0
- data/lib/shared.rb +10 -0
- data/spec/either_spec.rb +131 -0
- data/spec/maybe_spec.rb +135 -0
- data/spec/spec_helper.rb +19 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 222591a7509d0202f6c98d6ec6ed2158bbf225c8013f0b63bb325052233ee521
|
4
|
+
data.tar.gz: 987d4f4c9999e723d40af1ddf18ec5b518df1f23206e91e8d36aa58970076da4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c831346698f2c59a15a5b919a845c8873693f5db793f576c27c43161ebaf386c99e7a1897c9ad7fb340b789986d828e6e3262c85627db26ebde31eb6f4e910f6
|
7
|
+
data.tar.gz: bce4d0ddd8cb24d7ba242baf513825569d6491abe18062eee64ac0a664c9459e8c99e60d3afff7d0f565ec2a6a2c2f1a18e88de3436712b4f9cc7e1288d6ff5c
|
data/.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
Gemfile.lock
|
46
|
+
.ruby-version
|
47
|
+
.ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
# Failure is always an option
|
2
|
+
|
3
|
+
This library implements ruby-ified `Maybe` and `Either` types. Both capture
|
4
|
+
`nil` and `Exception` and return an object that implements the functor and monad
|
5
|
+
interfaces.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
### Maybe
|
10
|
+
|
11
|
+
`Maybe.new` takes a block and returns `Just <value>` on success and `Nothing` if
|
12
|
+
the block either returns `nil` or raises an exception.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
Maybe.new do
|
16
|
+
[1, 2, 3].first
|
17
|
+
end
|
18
|
+
|
19
|
+
#=> Just 1
|
20
|
+
|
21
|
+
Maybe.new do
|
22
|
+
[].first
|
23
|
+
end
|
24
|
+
|
25
|
+
#=> Nothing
|
26
|
+
```
|
27
|
+
|
28
|
+
`#map` is implemented on `Just` and `Nothing`, and is the functor `fmap`:
|
29
|
+
|
30
|
+
```haskell
|
31
|
+
map :: Maybe a -> (a -> b) -> Maybe b
|
32
|
+
```
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
Maybe.new do
|
36
|
+
[1, 2, 3].first
|
37
|
+
end.map do |i|
|
38
|
+
i * 100
|
39
|
+
end
|
40
|
+
|
41
|
+
#=> Just 100
|
42
|
+
|
43
|
+
Maybe.new do
|
44
|
+
[].first
|
45
|
+
end.map do |i|
|
46
|
+
i * 100
|
47
|
+
end
|
48
|
+
|
49
|
+
#=> Nothing
|
50
|
+
```
|
51
|
+
|
52
|
+
`#flat_map` is implemented on `Just` and `Nothing`, and is the monad `bind`:
|
53
|
+
|
54
|
+
```haskell
|
55
|
+
flat_map :: Maybe a -> (a -> Maybe b) -> Maybe b
|
56
|
+
```
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
Maybe.new do
|
60
|
+
[[1], [2], [3]].first
|
61
|
+
end.flat_map do |i|
|
62
|
+
Maybe.new { i.first }
|
63
|
+
end
|
64
|
+
|
65
|
+
#=> Just 1
|
66
|
+
|
67
|
+
Maybe.new do
|
68
|
+
[1, 2, 3].first
|
69
|
+
end.flat_map do |i|
|
70
|
+
Maybe.new { i.first } # raises NoMethodError
|
71
|
+
end
|
72
|
+
|
73
|
+
#=> Nothing
|
74
|
+
```
|
75
|
+
|
76
|
+
`Maybe.match` takes the maybe, a proc for the `Just` and a proc of no arguments
|
77
|
+
for the `Nothing` and returns the value of the associated variant. Because there
|
78
|
+
is no value for `Nothing`, a default value may be provided instead of a proc.
|
79
|
+
|
80
|
+
|
81
|
+
```haskell
|
82
|
+
match :: Maybe a -> (a -> b) -> b -> b
|
83
|
+
```
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
just = Maybe.new do
|
87
|
+
[1, 2, 3].first
|
88
|
+
end
|
89
|
+
|
90
|
+
nothing = Maybe.new do
|
91
|
+
[].first
|
92
|
+
end
|
93
|
+
|
94
|
+
Maybe.match(just,
|
95
|
+
just: ->(i) { i + 1 },
|
96
|
+
nothing: 0
|
97
|
+
)
|
98
|
+
|
99
|
+
#=> 2
|
100
|
+
|
101
|
+
Maybe.match(nothing,
|
102
|
+
just: ->(i) { i + 1 },
|
103
|
+
nothing: 0
|
104
|
+
)
|
105
|
+
|
106
|
+
#=> 0
|
107
|
+
```
|
108
|
+
|
109
|
+
### Either
|
110
|
+
|
111
|
+
`Either` is similar to `Maybe` except that it preserves an error message from a
|
112
|
+
captured exception, or (trivially) the `nil` returned from a failed function.
|
113
|
+
|
114
|
+
`Either.new` takes a block and returns `Right <value>` if the block "succeeds",
|
115
|
+
and `Left <err | nil>` if the block raises or returns `nil`.
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
Either.new do
|
119
|
+
[1, 2, 3].first
|
120
|
+
end
|
121
|
+
|
122
|
+
#=> Right 1
|
123
|
+
|
124
|
+
Either.new do
|
125
|
+
1.first
|
126
|
+
end
|
127
|
+
|
128
|
+
#=> Left NoMethodError
|
129
|
+
```
|
130
|
+
|
131
|
+
`#map` is implemented on `Right` and `Left` and is the functor `fmap`
|
132
|
+
|
133
|
+
```haskell
|
134
|
+
map :: Either a b -> (b -> c) -> Either a c
|
135
|
+
```
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
Either.new do
|
139
|
+
[1, 2, 3].first
|
140
|
+
end.map do |i|
|
141
|
+
i * 100
|
142
|
+
end
|
143
|
+
|
144
|
+
#=> Right 100
|
145
|
+
|
146
|
+
Either.new do
|
147
|
+
1.first
|
148
|
+
end.map do |i|
|
149
|
+
i * 100
|
150
|
+
end
|
151
|
+
|
152
|
+
#=> Left NoMethodError
|
153
|
+
```
|
154
|
+
|
155
|
+
`#flat_map` is implemented on `Right` and `Left` and is the monad `bind`
|
156
|
+
|
157
|
+
```haskell
|
158
|
+
flat_map :: Either a b -> (b -> Either a c) -> Either a c
|
159
|
+
```
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
Either.new do
|
163
|
+
[[1], [2], [3]].first
|
164
|
+
end.flat_map do |i|
|
165
|
+
Either.new { i.first }
|
166
|
+
end
|
167
|
+
|
168
|
+
#=> Right 1
|
169
|
+
|
170
|
+
Either.new do
|
171
|
+
[1, 2, 3].first
|
172
|
+
end.flat_map do |i|
|
173
|
+
Either.new { i.first }
|
174
|
+
end
|
175
|
+
|
176
|
+
#=> Left NoMethodError
|
177
|
+
```
|
178
|
+
|
179
|
+
`Either.match` takes an either, a proc for `Right` and a proc for `Left`,
|
180
|
+
evaluates the proc corresponding to the variant, and returns the result.
|
181
|
+
|
182
|
+
```haskell
|
183
|
+
match :: Either a b -> (a -> c) (b -> c) -> c
|
184
|
+
```
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
right = Either.new do
|
188
|
+
[1, 2, 3].first
|
189
|
+
end
|
190
|
+
|
191
|
+
left = Either.new do
|
192
|
+
1.first
|
193
|
+
end
|
194
|
+
|
195
|
+
Either.match(right,
|
196
|
+
right: ->(i) { i * 100 },
|
197
|
+
left: ->(e) { e.message }
|
198
|
+
)
|
199
|
+
|
200
|
+
#=> 100
|
201
|
+
|
202
|
+
Either.match(left,
|
203
|
+
right: ->(i) { i * 100 },
|
204
|
+
left: ->(e) { e.message }
|
205
|
+
)
|
206
|
+
|
207
|
+
#=> "NoMethodError (undefined method `first' for 1:Integer)"
|
208
|
+
```
|
209
|
+
|
210
|
+
## Type Safety
|
211
|
+
|
212
|
+
There isn't any. Users are free to reach inside the classes using
|
213
|
+
`instance_variable_get` or `send`, and users can use `flat_map` with a block
|
214
|
+
that doesn't return an either to "unwrap" the value. And nothing constrains the
|
215
|
+
two procs in `.match` to return the same type. If we wanted type safety, we'd
|
216
|
+
use Haskell or Rust. But, just like everything else in ruby, this can be used to
|
217
|
+
make programming nicer.
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
desc 'Default: run specs.'
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc 'Run specs'
|
9
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
|
+
|
11
|
+
desc 'Generate code coverage'
|
12
|
+
RSpec::Core::RakeTask.new(:coverage)
|
data/failure.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'failure'
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.date = '2018-04-18'
|
5
|
+
s.summary = "Either and Option functor/applicative/monad"
|
6
|
+
s.description = "Implements Either and Option a la Haskell"
|
7
|
+
s.authors = ["Stuart Terrett"]
|
8
|
+
s.email = 'shterrett@gmail.com'
|
9
|
+
s.files = ["lib/failure.rb"]
|
10
|
+
s.homepage = 'http://github.com/shterrett/failure'
|
11
|
+
s.license = 'MIT'
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
16
|
+
s.require_paths = ['lib']
|
17
|
+
|
18
|
+
s.add_development_dependency("rspec", "~> 3.7")
|
19
|
+
s.add_development_dependency("rake", "~> 12.3")
|
20
|
+
s.add_development_dependency("simplecov", "~> 0.16")
|
21
|
+
s.add_development_dependency("pry", "~> 0.11")
|
22
|
+
end
|
data/lib/either.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative "./shared"
|
2
|
+
|
3
|
+
module Either
|
4
|
+
def self.new(&block)
|
5
|
+
val = begin
|
6
|
+
block.call()
|
7
|
+
rescue Exception => e
|
8
|
+
e
|
9
|
+
end
|
10
|
+
if val.nil? || val.is_a?(Exception)
|
11
|
+
Left.new(val)
|
12
|
+
else
|
13
|
+
Right.new(val)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.match(e, left:, right:)
|
18
|
+
if e.right?
|
19
|
+
right.call(e.send(:val))
|
20
|
+
elsif e.left?
|
21
|
+
left.call(e.send(:val))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Left
|
26
|
+
include Shared
|
27
|
+
|
28
|
+
def left?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
def right?
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(val)
|
36
|
+
@val = val
|
37
|
+
end
|
38
|
+
|
39
|
+
def map(&block)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def flat_map(&block)
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
attr_reader :val
|
49
|
+
end
|
50
|
+
|
51
|
+
class Right
|
52
|
+
include Shared
|
53
|
+
|
54
|
+
def left?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
def right?
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(val)
|
62
|
+
@val = val
|
63
|
+
end
|
64
|
+
|
65
|
+
def map
|
66
|
+
Either.new { yield val }
|
67
|
+
end
|
68
|
+
|
69
|
+
def flat_map
|
70
|
+
yield val
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
attr_reader :val
|
75
|
+
end
|
76
|
+
end
|
data/lib/failure.rb
ADDED
data/lib/maybe.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require_relative "./shared"
|
2
|
+
|
3
|
+
module Maybe
|
4
|
+
def self.new(&block)
|
5
|
+
val = begin
|
6
|
+
block.call()
|
7
|
+
rescue Exception => e
|
8
|
+
e
|
9
|
+
end
|
10
|
+
if val.nil? || val.is_a?(Exception)
|
11
|
+
Nothing.new
|
12
|
+
else
|
13
|
+
Just.new val
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.match(m, just:, nothing:)
|
18
|
+
if m.is_just?
|
19
|
+
just.call(m.send(:val))
|
20
|
+
elsif m.is_nothing?
|
21
|
+
if nothing.is_a? Proc
|
22
|
+
nothing.call
|
23
|
+
else
|
24
|
+
nothing
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Just
|
30
|
+
include Shared
|
31
|
+
|
32
|
+
def is_just?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
def is_nothing?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(val)
|
40
|
+
@val = val
|
41
|
+
end
|
42
|
+
|
43
|
+
def map
|
44
|
+
Maybe.new { yield val }
|
45
|
+
end
|
46
|
+
|
47
|
+
def flat_map
|
48
|
+
yield val
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
attr_reader :val
|
53
|
+
end
|
54
|
+
|
55
|
+
class Nothing
|
56
|
+
include Shared
|
57
|
+
|
58
|
+
def is_just?
|
59
|
+
false
|
60
|
+
end
|
61
|
+
def is_nothing?
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def map(&block)
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def flat_map(&block)
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def val
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/shared.rb
ADDED
data/spec/either_spec.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Either do
|
4
|
+
describe "new" do
|
5
|
+
it "returns a Right when given a non-nil value" do
|
6
|
+
e = Either.new do
|
7
|
+
5
|
8
|
+
end
|
9
|
+
|
10
|
+
expect(e.left?).to be_falsey
|
11
|
+
expect(e.right?).to be_truthy
|
12
|
+
expect(e).to be_an_instance_of Either::Right
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns a Left when given a nil value" do
|
16
|
+
e = Either.new do
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
expect(e.left?).to be_truthy
|
21
|
+
expect(e.right?).to be_falsey
|
22
|
+
expect(e).to be_an_instance_of Either::Left
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns a Left wrapping a returned exception" do
|
26
|
+
e = Either.new do
|
27
|
+
1 / 0
|
28
|
+
end
|
29
|
+
|
30
|
+
expect(e.left?).to be_truthy
|
31
|
+
expect(e.right?).to be_falsey
|
32
|
+
expect(e).to be_an_instance_of Either::Left
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "==" do
|
37
|
+
it "is false if one side is Left and the other is Right" do
|
38
|
+
r = Either.new { 5 }
|
39
|
+
l = Either.new { nil }
|
40
|
+
fake = Either::Right.new(nil)
|
41
|
+
|
42
|
+
expect(r == l).to be_falsey
|
43
|
+
expect(l == r).to be_falsey
|
44
|
+
expect(r == fake).to be_falsey
|
45
|
+
end
|
46
|
+
|
47
|
+
it "delegates to the value equality when comparing the same variant" do
|
48
|
+
r_1 = Either.new { 5 }
|
49
|
+
r_2 = Either.new { 6 }
|
50
|
+
r_3 = Either.new { 5 }
|
51
|
+
|
52
|
+
expect(r_1).not_to eq r_2
|
53
|
+
expect(r_1).to eq r_3
|
54
|
+
|
55
|
+
l_1 = Either.new { nil }
|
56
|
+
l_2 = Either.new { 1 / 0 }
|
57
|
+
l_3 = Either.new { nil }
|
58
|
+
|
59
|
+
expect(l_1).not_to eq l_2
|
60
|
+
expect(l_1).to eq l_3
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "match" do
|
65
|
+
it "runs the function given for Right and returns the result if the variant is a Right" do
|
66
|
+
e = Either::Right.new(5)
|
67
|
+
result = Either.match(
|
68
|
+
e,
|
69
|
+
left: ->(v){ v + 5 },
|
70
|
+
right: ->(v){ v * 5 }
|
71
|
+
)
|
72
|
+
|
73
|
+
expect(result).to eq 25
|
74
|
+
end
|
75
|
+
|
76
|
+
it "runs the function given for Left and returns the result if the variant is a Left" do
|
77
|
+
e = Either::Left.new(5)
|
78
|
+
result = Either.match(
|
79
|
+
e,
|
80
|
+
left: ->(v){ v + 5 },
|
81
|
+
right: ->(v){ v * 5 }
|
82
|
+
)
|
83
|
+
|
84
|
+
expect(result).to eq 10
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe Either::Left do
|
90
|
+
describe "map" do
|
91
|
+
it "returns itself, ignoring the function" do
|
92
|
+
l = Either::Left.new(nil).map do |v|
|
93
|
+
v * 5
|
94
|
+
end
|
95
|
+
|
96
|
+
expect(l).to eq Either::Left.new(nil)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "flat_map" do
|
101
|
+
it "returns itself, ignoring the function" do
|
102
|
+
l = Either::Left.new(nil).map do |v|
|
103
|
+
Either.new { v / 0 }
|
104
|
+
end
|
105
|
+
|
106
|
+
expect(l).to eq Either::Left.new(nil)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe Either::Right do
|
112
|
+
describe "map" do
|
113
|
+
it "applies the function to the wrapped value and returns the wrapped result" do
|
114
|
+
r = Either::Right.new(5).map do |v|
|
115
|
+
v + 5
|
116
|
+
end
|
117
|
+
|
118
|
+
expect(r).to eq Either::Right.new(10)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "flat_map" do
|
123
|
+
it "applies the function to the wrapped value and returns the resulting Either" do
|
124
|
+
r = Either::Right.new(5).flat_map do |v|
|
125
|
+
Either.new { v / 0 }
|
126
|
+
end
|
127
|
+
|
128
|
+
expect(r.left?).to be_truthy
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/spec/maybe_spec.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Maybe do
|
4
|
+
describe "new" do
|
5
|
+
it "returns a Just when given a non-nil value" do
|
6
|
+
m = Maybe.new do
|
7
|
+
5
|
8
|
+
end
|
9
|
+
|
10
|
+
expect(m.is_just?).to be_truthy
|
11
|
+
expect(m.is_nothing?).to be_falsey
|
12
|
+
expect(m).to be_an_instance_of Maybe::Just
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns a Nothing when given a nil value"do
|
16
|
+
m = Maybe.new do
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
expect(m.is_just?).to be_falsey
|
21
|
+
expect(m.is_nothing?).to be_truthy
|
22
|
+
expect(m).to be_an_instance_of Maybe::Nothing
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns a Nothing if the expression throws an exception" do
|
26
|
+
m = Maybe.new do
|
27
|
+
1 / 0
|
28
|
+
end
|
29
|
+
|
30
|
+
expect(m.is_just?).to be_falsey
|
31
|
+
expect(m.is_nothing?).to be_truthy
|
32
|
+
expect(m).to be_an_instance_of Maybe::Nothing
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "==" do
|
37
|
+
it "is false if one side is Just and the other is Nothing" do
|
38
|
+
j = Maybe.new { 5 }
|
39
|
+
n = Maybe.new { nil }
|
40
|
+
fake = Maybe::Just.new(nil)
|
41
|
+
|
42
|
+
expect(j == n).to be_falsey
|
43
|
+
expect(n == j).to be_falsey
|
44
|
+
expect(j == fake).to be_falsey
|
45
|
+
end
|
46
|
+
|
47
|
+
it "delegates to the value when comparing Just values" do
|
48
|
+
j_1 = Maybe.new { 5 }
|
49
|
+
j_2 = Maybe.new { 6 }
|
50
|
+
j_3 = Maybe.new { 5 }
|
51
|
+
|
52
|
+
expect(j_1).not_to eq j_2
|
53
|
+
expect(j_1).to eq j_3
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "match" do
|
58
|
+
it "runs the function given for Just and returns the result if the variant is Just" do
|
59
|
+
m = Maybe::Just.new(5)
|
60
|
+
result = Maybe.match(
|
61
|
+
m,
|
62
|
+
just: ->(v) { v + 5 },
|
63
|
+
nothing: -> { 0 }
|
64
|
+
)
|
65
|
+
|
66
|
+
expect(result).to eq 10
|
67
|
+
end
|
68
|
+
|
69
|
+
it "runs the function given for Nothing and returns the result if the variant is Nothing" do
|
70
|
+
m = Maybe::Nothing.new
|
71
|
+
result = Maybe.match(
|
72
|
+
m,
|
73
|
+
just: ->(v) { v + 5 },
|
74
|
+
nothing: -> { 0 }
|
75
|
+
)
|
76
|
+
|
77
|
+
expect(result).to eq 0
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns the static default given for Nothing if the variant is Nothing" do
|
81
|
+
m = Maybe::Nothing.new
|
82
|
+
result = Maybe.match(
|
83
|
+
m,
|
84
|
+
just: ->(v) { v + 5 },
|
85
|
+
nothing: 0
|
86
|
+
)
|
87
|
+
|
88
|
+
expect(result).to eq 0
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe Maybe::Nothing do
|
94
|
+
describe "map" do
|
95
|
+
it "returns itself, ignoring the function" do
|
96
|
+
n = Maybe::Nothing.new.map do |v|
|
97
|
+
v * 5
|
98
|
+
end
|
99
|
+
|
100
|
+
expect(n).to eq Maybe::Nothing.new
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "flat_map" do
|
105
|
+
it "returns itself, ignoring the function" do
|
106
|
+
n = Maybe::Nothing.new.flat_map do |v|
|
107
|
+
Maybe.new { v * 5 }
|
108
|
+
end
|
109
|
+
|
110
|
+
expect(n).to eq Maybe::Nothing.new
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe Maybe::Just do
|
116
|
+
describe "map" do
|
117
|
+
it "applies the function to the wrapped value and returns the wrapped result" do
|
118
|
+
j = Maybe::Just.new(5).map do |v|
|
119
|
+
v * 5
|
120
|
+
end
|
121
|
+
|
122
|
+
expect(j).to eq Maybe::Just.new(25)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "flat_map" do
|
127
|
+
it "applies the function to the wrapped value and returns the resulting Maybe" do
|
128
|
+
m = Maybe::Just.new(5).flat_map do |v|
|
129
|
+
Maybe.new { v / 0 }
|
130
|
+
end
|
131
|
+
|
132
|
+
expect(m.is_nothing?).to be_truthy
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require "pry"
|
3
|
+
|
4
|
+
SimpleCov.start do
|
5
|
+
add_filter 'spec/'
|
6
|
+
end
|
7
|
+
|
8
|
+
# encoding: utf-8
|
9
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
10
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
11
|
+
|
12
|
+
Dir['./spec/shared/**/*.rb'].sort.each { |f| require f }
|
13
|
+
require_relative '../lib/failure'
|
14
|
+
|
15
|
+
require 'rspec'
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.order = 'random'
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: failure
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stuart Terrett
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-04-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '12.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '12.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.16'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.16'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.11'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.11'
|
69
|
+
description: Implements Either and Option a la Haskell
|
70
|
+
email: shterrett@gmail.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- ".gitignore"
|
76
|
+
- Gemfile
|
77
|
+
- README.md
|
78
|
+
- Rakefile
|
79
|
+
- failure.gemspec
|
80
|
+
- lib/either.rb
|
81
|
+
- lib/failure.rb
|
82
|
+
- lib/maybe.rb
|
83
|
+
- lib/shared.rb
|
84
|
+
- spec/either_spec.rb
|
85
|
+
- spec/maybe_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
homepage: http://github.com/shterrett/failure
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.7.3
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Either and Option functor/applicative/monad
|
111
|
+
test_files:
|
112
|
+
- spec/either_spec.rb
|
113
|
+
- spec/maybe_spec.rb
|
114
|
+
- spec/spec_helper.rb
|