fear 0.0.1 → 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 +4 -4
- data/.rubocop.yml +0 -24
- data/README.md +133 -2
- data/lib/fear/either.rb +21 -8
- data/lib/fear/failure.rb +1 -2
- data/lib/fear/left.rb +1 -1
- data/lib/fear/none.rb +1 -1
- data/lib/fear/option.rb +8 -15
- data/lib/fear/right.rb +1 -1
- data/lib/fear/right_biased.rb +1 -1
- data/lib/fear/some.rb +1 -1
- data/lib/fear/success.rb +1 -1
- data/lib/fear/try.rb +6 -6
- data/lib/fear/version.rb +1 -1
- data/spec/fear/failure_spec.rb +2 -2
- data/spec/fear/left_spec.rb +17 -5
- data/spec/fear/none_spec.rb +2 -2
- data/spec/fear/right_spec.rb +14 -4
- data/spec/fear/some_spec.rb +2 -2
- data/spec/fear/success_spec.rb +4 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fa320f98cb5dc71a7b4e59d7a500c01b729f449
|
4
|
+
data.tar.gz: ea1935382f1bed41adab1264490f3346ff7d93c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49da39785143896e2c6ae73b86765fd7aefdac8603ecbf2bd2d4516cce164dd5946fb9041201f7738eca4fbe8c1e6d78760696a0717d5918c36a7dfe48bfc6ff
|
7
|
+
data.tar.gz: 66404d368eb7162f626a4f822b4d4cc0975888f764ffa2bbb31354227a04b795ba8ced9f0ee769880aa6831dc90759f24ea87769a39d88795d8485228e053896
|
data/.rubocop.yml
CHANGED
@@ -4,29 +4,5 @@ inherit_gem:
|
|
4
4
|
Style/MethodName:
|
5
5
|
Enabled: false
|
6
6
|
|
7
|
-
# This configuration was generated by `rubocop --auto-gen-config`
|
8
|
-
# on 2015-03-09 00:50:30 +0300 using RuboCop version 0.29.1.
|
9
|
-
# The point is for the user to remove these configuration records
|
10
|
-
# one by one as the offenses are removed from the code base.
|
11
|
-
# Note that changes in the inspected code, or installation of new
|
12
|
-
# versions of RuboCop, may require this file to be generated again.
|
13
|
-
|
14
|
-
# Offense count: 1
|
15
|
-
# Configuration parameters: CountComments.
|
16
|
-
Metrics/ClassLength:
|
17
|
-
Enabled: false
|
18
|
-
Max: 133
|
19
|
-
|
20
|
-
# Offense count: 1
|
21
|
-
# Configuration parameters: CountComments.
|
22
|
-
Metrics/ModuleLength:
|
23
|
-
Max: 130
|
24
|
-
|
25
|
-
# Offense count: 9
|
26
7
|
Style/Documentation:
|
27
8
|
Enabled: false
|
28
|
-
|
29
|
-
# Offense count: 2
|
30
|
-
# Configuration parameters: Methods.
|
31
|
-
Style/SingleLineBlockParams:
|
32
|
-
Enabled: false
|
data/README.md
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# Fear
|
2
2
|
[](https://travis-ci.org/bolshakov/fear)
|
3
|
+
[](https://badge.fury.io/rb/fear)
|
3
4
|
|
4
|
-
|
5
|
+
This gem provides `Option`, `Either`, and `Try` monads implemented an idiomatic way.
|
6
|
+
It is highly inspired by scala's implementation.
|
5
7
|
|
6
8
|
## Installation
|
7
9
|
|
@@ -21,7 +23,136 @@ Or install it yourself as:
|
|
21
23
|
|
22
24
|
## Usage
|
23
25
|
|
24
|
-
|
26
|
+
### Option
|
27
|
+
|
28
|
+
Represents optional values. Instances of `Option` are either an instance of
|
29
|
+
`Some` or the object `None`.
|
30
|
+
|
31
|
+
The most idiomatic way to use an `Option` instance is to treat it
|
32
|
+
as a collection and use `map`, `flat_map`, `select`, or `each`:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
name = Option(params[:name])
|
36
|
+
upper = name.map(&:strip).select { |n| n.length != 0 }.map(&:upcase)
|
37
|
+
puts upper.get_or_else('')
|
38
|
+
```
|
39
|
+
|
40
|
+
This allows for sophisticated chaining of `Option` values without
|
41
|
+
having to check for the existence of a value.
|
42
|
+
|
43
|
+
See full documentation [Fear::Option](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Option)
|
44
|
+
|
45
|
+
### Try
|
46
|
+
|
47
|
+
The `Try` represents a computation that may either result in an exception,
|
48
|
+
or return a successfully computed value. Instances of `Try`, are either
|
49
|
+
an instance of `Success` or `Failure`.
|
50
|
+
|
51
|
+
For example, `Try` can be used to perform division on a user-defined input,
|
52
|
+
without the need to do explicit exception-handling in all of the places
|
53
|
+
that an exception might occur.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
dividend = Try { Integer(params[:dividend]) }
|
57
|
+
divisor = Try { Integer(params[:divisor]) }
|
58
|
+
|
59
|
+
problem = dividend.flat_map { |x| divisor.map { |y| x / y }
|
60
|
+
|
61
|
+
if problem.success?
|
62
|
+
puts "Result of #{dividend.get} / #{divisor.get} is: #{problem.get}"
|
63
|
+
else
|
64
|
+
puts "You must've divided by zero or entered something wrong. Try again"
|
65
|
+
puts "Info from the exception: #{problem.exception.message}"
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
See full documentation [Fear::Try](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Try)
|
70
|
+
|
71
|
+
### Either
|
72
|
+
|
73
|
+
Represents a value of one of two possible types (a disjoint union.)
|
74
|
+
An instance of `Either` is either an instance of `Left` or `Right`.
|
75
|
+
|
76
|
+
A common use of `Either` is as an alternative to `Option` for dealing
|
77
|
+
with possible missing values. In this usage, `None` is replaced
|
78
|
+
with a `Left` which can contain useful information.
|
79
|
+
`Right` takes the place of `Some`. Convention dictates
|
80
|
+
that `Left` is used for failure and `Right` is used for success.
|
81
|
+
|
82
|
+
For example, you could use `Either<String, Fixnum>` to select whether a
|
83
|
+
received input is a `String` or an `Fixnum`.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
input = Readline.readline('Type Either a string or an Int: ', true)
|
87
|
+
result = begin
|
88
|
+
Right(Integer(input))
|
89
|
+
rescue ArgumentError
|
90
|
+
Left(input)
|
91
|
+
end
|
92
|
+
|
93
|
+
puts(
|
94
|
+
result.reduce(
|
95
|
+
-> (x) { "You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}" },
|
96
|
+
-> (x) { "You passed me the String: #{x}" }
|
97
|
+
)
|
98
|
+
)
|
99
|
+
```
|
100
|
+
|
101
|
+
See full documentation [Fear::Either](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Either)
|
102
|
+
|
103
|
+
### For composition
|
104
|
+
|
105
|
+
Provides syntactic sugar for composition of multiple monadic operations.
|
106
|
+
It supports two such operations - `flat_map` and `map`. Any class providing them
|
107
|
+
is supported by `For`.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
For(a: Some(2), b: Some(3)) do
|
111
|
+
a * b
|
112
|
+
end #=> Some(6)
|
113
|
+
```
|
114
|
+
|
115
|
+
It would be translated to
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
Some(2).flat_map do |a|
|
119
|
+
Some(3).map do |b|
|
120
|
+
a * b
|
121
|
+
end
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
If one of operands is None, the result is None
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
For(a: Some(2), b: None()) do
|
129
|
+
a * b
|
130
|
+
end #=> None()
|
131
|
+
|
132
|
+
For(a: None(), b: Some(2)) do
|
133
|
+
a * b
|
134
|
+
end #=> None()
|
135
|
+
```
|
136
|
+
|
137
|
+
`For` works with arrays as well
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
For(a: [1, 2], b: [2, 3], c: [3, 4]) do
|
141
|
+
a * b * c
|
142
|
+
end #=> [6, 8, 9, 12, 12, 16, 18, 24]
|
143
|
+
```
|
144
|
+
|
145
|
+
would be translated to:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
[1, 2].flat_map do |a|
|
149
|
+
[2, 3].flat_map do |b|
|
150
|
+
[3, 4].map do |c|
|
151
|
+
a * b * c
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
```
|
25
156
|
|
26
157
|
## Contributing
|
27
158
|
|
data/lib/fear/either.rb
CHANGED
@@ -8,7 +8,7 @@ module Fear
|
|
8
8
|
# `Right` takes the place of `Some`. Convention dictates
|
9
9
|
# that `Left` is used for failure and `Right` is used for success.
|
10
10
|
#
|
11
|
-
# For example, you could use `Either<String, Fixnum>` to
|
11
|
+
# For example, you could use `Either<String, Fixnum>` to select whether a
|
12
12
|
# received input is a `String` or an `Fixnum`.
|
13
13
|
#
|
14
14
|
# @example
|
@@ -26,7 +26,7 @@ module Fear
|
|
26
26
|
# )
|
27
27
|
# )
|
28
28
|
#
|
29
|
-
#
|
29
|
+
# Either is right-biased, which means that `Right` is assumed to be the default case to
|
30
30
|
# operate on. If it is `Left`, operations like `#map`, `#flat_map`, ... return the `Left` value
|
31
31
|
# unchanged:
|
32
32
|
#
|
@@ -63,11 +63,11 @@ module Fear
|
|
63
63
|
# Right(12).flat_map { |x| Left('ruby') } #=> Left('ruby')
|
64
64
|
# Left(12).flat_map { |x| Left('ruby') } #=> Left(12)
|
65
65
|
#
|
66
|
-
# @example #
|
67
|
-
# Right(12).
|
68
|
-
# Right(7).
|
69
|
-
# Left(12).
|
70
|
-
# Left(12).
|
66
|
+
# @example #select
|
67
|
+
# Right(12).select(-1, &:even?) #=> Right(12))
|
68
|
+
# Right(7).select(-1, &:even?) #=> Left(-1)
|
69
|
+
# Left(12).select(-1, &:even?) #=> Left(-1)
|
70
|
+
# Left(12).select(-> { -1 }, &:even?) #=> Left(-1)
|
71
71
|
#
|
72
72
|
# @example #to_a
|
73
73
|
# Right(12).to_a #=> [12]
|
@@ -111,7 +111,6 @@ module Fear
|
|
111
111
|
#
|
112
112
|
module Either
|
113
113
|
include Dry::Equalizer(:value)
|
114
|
-
include Fear
|
115
114
|
|
116
115
|
def left_class
|
117
116
|
Left
|
@@ -125,6 +124,20 @@ module Fear
|
|
125
124
|
@value = value
|
126
125
|
end
|
127
126
|
|
127
|
+
# @return [Boolean]
|
128
|
+
def right?
|
129
|
+
is_a?(Right)
|
130
|
+
end
|
131
|
+
|
132
|
+
alias success? right?
|
133
|
+
|
134
|
+
# @return [Boolean]
|
135
|
+
def left?
|
136
|
+
!right?
|
137
|
+
end
|
138
|
+
|
139
|
+
alias failure? left?
|
140
|
+
|
128
141
|
attr_reader :value
|
129
142
|
protected :value
|
130
143
|
|
data/lib/fear/failure.rb
CHANGED
data/lib/fear/left.rb
CHANGED
data/lib/fear/none.rb
CHANGED
data/lib/fear/option.rb
CHANGED
@@ -2,20 +2,15 @@ module Fear
|
|
2
2
|
# Represents optional values. Instances of `Option`
|
3
3
|
# are either an instance of `Some` or the object `None`.
|
4
4
|
#
|
5
|
-
# The most idiomatic way to use an `Option` instance is to treat it
|
6
|
-
# as a collection or monad and use `map`, `flat_map`, `detect`, or `each`:
|
7
|
-
#
|
8
|
-
# @example
|
5
|
+
# @example The most idiomatic way to use an `Option` instance is to treat it as a collection
|
9
6
|
# name = Option(params[:name])
|
10
|
-
# upper = name.map(&:strip).
|
7
|
+
# upper = name.map(&:strip).select { |n| n.length != 0 }.map(&:upcase)
|
11
8
|
# puts upper.get_or_else('')
|
12
9
|
#
|
13
10
|
# This allows for sophisticated chaining of `Option` values without
|
14
11
|
# having to check for the existence of a value.
|
15
12
|
#
|
16
|
-
# A less-idiomatic way to use `Option` values is via pattern matching
|
17
|
-
#
|
18
|
-
# @example
|
13
|
+
# @example A less-idiomatic way to use `Option` values is via pattern matching
|
19
14
|
# name = Option(params[:name])
|
20
15
|
# case name
|
21
16
|
# when Some
|
@@ -24,9 +19,7 @@ module Fear
|
|
24
19
|
# puts 'No name value'
|
25
20
|
# end
|
26
21
|
#
|
27
|
-
# or manually checking for non emptiness
|
28
|
-
#
|
29
|
-
# @example
|
22
|
+
# @example or manually checking for non emptiness
|
30
23
|
# name = Option(params[:name])
|
31
24
|
# if name.empty?
|
32
25
|
# puts 'No name value'
|
@@ -34,17 +27,17 @@ module Fear
|
|
34
27
|
# puts name.strip.upcase
|
35
28
|
# end
|
36
29
|
#
|
37
|
-
# @example #
|
38
|
-
# User.find(params[:id]).
|
30
|
+
# @example #select
|
31
|
+
# User.find(params[:id]).select do |user|
|
39
32
|
# user.posts.count > 0
|
40
33
|
# end #=> Some(User)
|
41
34
|
#
|
42
|
-
# User.find(params[:id]).
|
35
|
+
# User.find(params[:id]).select do |user|
|
43
36
|
# user.posts.count > 0
|
44
37
|
# end #=> None
|
45
38
|
#
|
46
39
|
# User.find(params[:id])
|
47
|
-
# .
|
40
|
+
# .select(&:confirmed?)
|
48
41
|
# .map(&:posts)
|
49
42
|
# .inject(0, &:count)
|
50
43
|
#
|
data/lib/fear/right.rb
CHANGED
data/lib/fear/right_biased.rb
CHANGED
data/lib/fear/some.rb
CHANGED
data/lib/fear/success.rb
CHANGED
data/lib/fear/try.rb
CHANGED
@@ -11,8 +11,8 @@ module Fear
|
|
11
11
|
# might occur.
|
12
12
|
#
|
13
13
|
# @example
|
14
|
-
# dividend = Try { params[:dividend]
|
15
|
-
# divisor = Try { params[:divisor]
|
14
|
+
# dividend = Try { Integer(params[:dividend]) }
|
15
|
+
# divisor = Try { Integer(params[:divisor]) }
|
16
16
|
# problem = dividend.flat_map { |x| divisor.map { |y| x / y }
|
17
17
|
#
|
18
18
|
# if problem.success?
|
@@ -53,12 +53,12 @@ module Fear
|
|
53
53
|
# Success(42).map { |v| v/2 } #=> Success(21)
|
54
54
|
# Failure(ArgumentError.new).map { |v| v/2 } #=> Failure(ArgumentError.new)
|
55
55
|
#
|
56
|
-
# @example #
|
57
|
-
# Success(42).
|
56
|
+
# @example #select
|
57
|
+
# Success(42).select { |v| v > 40 }
|
58
58
|
# #=> Success(21)
|
59
|
-
# Success(42).
|
59
|
+
# Success(42).select { |v| v < 40 }
|
60
60
|
# #=> Failure(NoSuchElementError.new("Predicate does not hold for 42"))
|
61
|
-
# Failure(ArgumentError.new).
|
61
|
+
# Failure(ArgumentError.new).select { |v| v < 40 }
|
62
62
|
# #=> Failure(ArgumentError.new)
|
63
63
|
#
|
64
64
|
# @example #recover_with
|
data/lib/fear/version.rb
CHANGED
data/spec/fear/failure_spec.rb
CHANGED
@@ -33,8 +33,8 @@ RSpec.describe Fear::Failure do
|
|
33
33
|
it { is_expected.to eq(failure) }
|
34
34
|
end
|
35
35
|
|
36
|
-
describe '#
|
37
|
-
subject { failure.
|
36
|
+
describe '#select' do
|
37
|
+
subject { failure.select { |v| v == 'value' } }
|
38
38
|
it { is_expected.to eq(failure) }
|
39
39
|
end
|
40
40
|
|
data/spec/fear/left_spec.rb
CHANGED
@@ -5,9 +5,21 @@ RSpec.describe Fear::Left do
|
|
5
5
|
let(:left) { described_class.new('value') }
|
6
6
|
end
|
7
7
|
|
8
|
-
|
8
|
+
let(:left) { described_class.new('value') }
|
9
|
+
|
10
|
+
describe '#right?' do
|
11
|
+
subject { left }
|
12
|
+
it { is_expected.not_to be_right }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#left?' do
|
16
|
+
subject { left }
|
17
|
+
it { is_expected.to be_left }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#select' do
|
9
21
|
subject do
|
10
|
-
|
22
|
+
left.select(default) { |v| v == 'value' }
|
11
23
|
end
|
12
24
|
|
13
25
|
context 'proc default' do
|
@@ -28,13 +40,13 @@ RSpec.describe Fear::Left do
|
|
28
40
|
end
|
29
41
|
|
30
42
|
describe '#swap' do
|
31
|
-
subject {
|
43
|
+
subject { left.swap }
|
32
44
|
it { is_expected.to eq(Right('value')) }
|
33
45
|
end
|
34
46
|
|
35
47
|
describe '#reduce' do
|
36
48
|
subject do
|
37
|
-
|
49
|
+
left.reduce(
|
38
50
|
->(left) { "Left: #{left}" },
|
39
51
|
->(right) { "Right: #{right}" },
|
40
52
|
)
|
@@ -69,7 +81,7 @@ RSpec.describe Fear::Left do
|
|
69
81
|
end
|
70
82
|
|
71
83
|
context 'value is not Either' do
|
72
|
-
subject { proc {
|
84
|
+
subject { proc { left.join_left } }
|
73
85
|
|
74
86
|
it 'fails with type error' do
|
75
87
|
is_expected.to raise_error(TypeError)
|
data/spec/fear/none_spec.rb
CHANGED
@@ -19,8 +19,8 @@ RSpec.describe Fear::None do
|
|
19
19
|
expect(result).to eq nil
|
20
20
|
end
|
21
21
|
|
22
|
-
describe '#
|
23
|
-
subject { none.
|
22
|
+
describe '#select' do
|
23
|
+
subject { none.select { |value| value > 42 } }
|
24
24
|
|
25
25
|
it 'always return None' do
|
26
26
|
is_expected.to eq(None())
|
data/spec/fear/right_spec.rb
CHANGED
@@ -5,8 +5,18 @@ RSpec.describe Fear::Right do
|
|
5
5
|
|
6
6
|
let(:right) { described_class.new('value') }
|
7
7
|
|
8
|
-
describe '#
|
9
|
-
subject { right
|
8
|
+
describe '#right?' do
|
9
|
+
subject { right }
|
10
|
+
it { is_expected.to be_right }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#left?' do
|
14
|
+
subject { right }
|
15
|
+
it { is_expected.not_to be_left }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#select' do
|
19
|
+
subject { right.select(default, &predicate) }
|
10
20
|
|
11
21
|
context 'predicate evaluates to true' do
|
12
22
|
let(:predicate) { ->(v) { v == 'value' } }
|
@@ -28,13 +38,13 @@ RSpec.describe Fear::Right do
|
|
28
38
|
end
|
29
39
|
|
30
40
|
describe '#swap' do
|
31
|
-
subject {
|
41
|
+
subject { right.swap }
|
32
42
|
it { is_expected.to eq(Fear::Left.new('value')) }
|
33
43
|
end
|
34
44
|
|
35
45
|
describe '#reduce' do
|
36
46
|
subject do
|
37
|
-
|
47
|
+
right.reduce(
|
38
48
|
->(left) { "Left: #{left}" },
|
39
49
|
->(right) { "Right: #{right}" },
|
40
50
|
)
|
data/spec/fear/some_spec.rb
CHANGED
@@ -8,8 +8,8 @@ RSpec.describe Fear::Some do
|
|
8
8
|
subject(:some) { Some(value) }
|
9
9
|
let(:value) { 42 }
|
10
10
|
|
11
|
-
describe '#
|
12
|
-
subject { some.
|
11
|
+
describe '#select' do
|
12
|
+
subject { some.select(&predicate) }
|
13
13
|
|
14
14
|
context 'predicate evaluates to true' do
|
15
15
|
let(:predicate) { ->(v) { v > 40 } }
|
data/spec/fear/success_spec.rb
CHANGED
@@ -54,19 +54,19 @@ RSpec.describe Fear::Success do
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
-
describe '#
|
57
|
+
describe '#select' do
|
58
58
|
context 'predicate holds for value' do
|
59
|
-
subject { success.
|
59
|
+
subject { success.select { |v| v == 'value' } }
|
60
60
|
it { is_expected.to eq(success) }
|
61
61
|
end
|
62
62
|
|
63
63
|
context 'predicate does not hold for value' do
|
64
|
-
subject { proc { success.
|
64
|
+
subject { proc { success.select { |v| v != 'value' }.get } }
|
65
65
|
it { is_expected.to raise_error(Fear::NoSuchElementError, 'Predicate does not hold for `value`') }
|
66
66
|
end
|
67
67
|
|
68
68
|
context 'predicate fails with error' do
|
69
|
-
subject { proc { success.
|
69
|
+
subject { proc { success.select { fail 'foo' }.get } }
|
70
70
|
it { is_expected.to raise_error(RuntimeError, 'foo') }
|
71
71
|
end
|
72
72
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fear
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tema Bolshakov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-equalizer
|