fear 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/bolshakov/fear.svg?branch=master)](https://travis-ci.org/bolshakov/fear)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/fear.svg)](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
|