fear 0.11.0 → 1.0.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 +5 -5
- data/.gitignore +0 -1
- data/.rubocop.yml +18 -0
- data/.travis.yml +0 -3
- data/CHANGELOG.md +12 -1
- data/Gemfile +1 -0
- data/{gemfiles/dry_equalizer_0.2.1.gemfile.lock → Gemfile.lock} +21 -12
- data/README.md +594 -241
- data/Rakefile +166 -219
- data/benchmarks/README.md +1 -0
- data/benchmarks/dry_do_vs_fear_for.txt +11 -0
- data/benchmarks/dry_some_fmap_vs_fear_some_map.txt +11 -0
- data/benchmarks/factorial.txt +16 -0
- data/benchmarks/fear_gaurd_and1_vs_new.txt +13 -0
- data/benchmarks/fear_gaurd_and2_vs_and.txt +13 -0
- data/benchmarks/fear_gaurd_and3_vs_and_and.txt +13 -0
- data/benchmarks/fear_pattern_extracting_with_vs_without_cache.txt +11 -0
- data/benchmarks/fear_pattern_matching_construction_vs_execution.txt +13 -0
- data/benchmarks/pattern_matching_dry_vs_qo_vs_fear_try.txt +14 -0
- data/benchmarks/pattern_matching_qo_vs_fear_pattern_extraction.txt +11 -0
- data/benchmarks/pattern_matching_qo_vs_fear_try_execution.txt +11 -0
- data/examples/pattern_extracting.rb +15 -0
- data/examples/pattern_matching_binary_tree_set.rb +96 -0
- data/examples/pattern_matching_number_in_words.rb +54 -0
- data/fear.gemspec +4 -2
- data/lib/fear.rb +21 -4
- data/lib/fear/either.rb +77 -59
- data/lib/fear/either_api.rb +21 -0
- data/lib/fear/empty_partial_function.rb +1 -1
- data/lib/fear/extractor.rb +108 -0
- data/lib/fear/extractor/anonymous_array_splat_matcher.rb +8 -0
- data/lib/fear/extractor/any_matcher.rb +15 -0
- data/lib/fear/extractor/array_head_matcher.rb +34 -0
- data/lib/fear/extractor/array_matcher.rb +38 -0
- data/lib/fear/extractor/array_splat_matcher.rb +14 -0
- data/lib/fear/extractor/empty_list_matcher.rb +18 -0
- data/lib/fear/extractor/extractor_matcher.rb +42 -0
- data/lib/fear/extractor/grammar.rb +201 -0
- data/lib/fear/extractor/grammar.treetop +129 -0
- data/lib/fear/extractor/identifier_matcher.rb +16 -0
- data/lib/fear/extractor/matcher.rb +54 -0
- data/lib/fear/extractor/matcher/and.rb +36 -0
- data/lib/fear/extractor/named_array_splat_matcher.rb +15 -0
- data/lib/fear/extractor/pattern.rb +55 -0
- data/lib/fear/extractor/typed_identifier_matcher.rb +24 -0
- data/lib/fear/extractor/value_matcher.rb +17 -0
- data/lib/fear/extractor_api.rb +33 -0
- data/lib/fear/failure.rb +32 -10
- data/lib/fear/for.rb +14 -69
- data/lib/fear/for_api.rb +66 -0
- data/lib/fear/future.rb +414 -0
- data/lib/fear/future_api.rb +19 -0
- data/lib/fear/left.rb +8 -0
- data/lib/fear/none.rb +17 -8
- data/lib/fear/option.rb +55 -49
- data/lib/fear/option_api.rb +38 -0
- data/lib/fear/partial_function.rb +9 -12
- data/lib/fear/partial_function/empty.rb +1 -1
- data/lib/fear/partial_function/guard.rb +8 -20
- data/lib/fear/partial_function/lifted.rb +1 -0
- data/lib/fear/partial_function_class.rb +10 -0
- data/lib/fear/pattern_match.rb +10 -0
- data/lib/fear/pattern_matching_api.rb +35 -11
- data/lib/fear/promise.rb +87 -0
- data/lib/fear/right.rb +8 -0
- data/lib/fear/some.rb +22 -3
- data/lib/fear/success.rb +22 -1
- data/lib/fear/try.rb +82 -67
- data/lib/fear/try_api.rb +31 -0
- data/lib/fear/unit.rb +28 -0
- data/lib/fear/version.rb +1 -1
- data/spec/fear/done_spec.rb +3 -3
- data/spec/fear/either/mixin_spec.rb +15 -0
- data/spec/fear/either_pattern_match_spec.rb +10 -12
- data/spec/fear/extractor/array_matcher_spec.rb +228 -0
- data/spec/fear/extractor/extractor_matcher_spec.rb +151 -0
- data/spec/fear/extractor/grammar_array_spec.rb +23 -0
- data/spec/fear/extractor/identified_matcher_spec.rb +47 -0
- data/spec/fear/extractor/identifier_matcher_spec.rb +66 -0
- data/spec/fear/extractor/pattern_spec.rb +32 -0
- data/spec/fear/extractor/typed_identifier_matcher_spec.rb +62 -0
- data/spec/fear/extractor/value_matcher_number_spec.rb +77 -0
- data/spec/fear/extractor/value_matcher_string_spec.rb +86 -0
- data/spec/fear/extractor/value_matcher_symbol_spec.rb +69 -0
- data/spec/fear/extractor_api_spec.rb +113 -0
- data/spec/fear/extractor_spec.rb +59 -0
- data/spec/fear/failure_spec.rb +73 -13
- data/spec/fear/for_spec.rb +35 -35
- data/spec/fear/future_spec.rb +466 -0
- data/spec/fear/guard_spec.rb +4 -4
- data/spec/fear/left_spec.rb +40 -14
- data/spec/fear/none_spec.rb +28 -12
- data/spec/fear/option/mixin_spec.rb +37 -0
- data/spec/fear/option_pattern_match_spec.rb +7 -9
- data/spec/fear/partial_function_spec.rb +25 -3
- data/spec/fear/pattern_match_spec.rb +33 -1
- data/spec/fear/promise_spec.rb +94 -0
- data/spec/fear/right_spec.rb +37 -9
- data/spec/fear/some_spec.rb +32 -6
- data/spec/fear/success_spec.rb +32 -4
- data/spec/fear/try/mixin_spec.rb +17 -0
- data/spec/fear/try_pattern_match_spec.rb +8 -10
- data/spec/spec_helper.rb +1 -1
- metadata +115 -20
- data/Appraisals +0 -32
- data/gemfiles/dry_equalizer_0.1.0.gemfile +0 -8
- data/gemfiles/dry_equalizer_0.1.0.gemfile.lock +0 -82
- data/gemfiles/dry_equalizer_0.2.1.gemfile +0 -8
- data/lib/fear/done.rb +0 -22
- data/spec/fear/option_spec.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b04f2a97dbf830e68f70c42b703e2e70f7baf389da09871b2024effe95f51c68
|
4
|
+
data.tar.gz: 7852518363fe60c10e9c60da873462e84e9b1ad567c760992bbf595b5eb388fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b166826f4b02237d1614e334b98991271b8825d98cdb879d863abacdfc72587c0dfa47fe8762cab3b29cd7fa8472a5069092e1f8766e68807f8311bfe1c1614
|
7
|
+
data.tar.gz: 8393f1845627f280e8e65caf2f4f36b329106beef35445ef58d3a8d053b8f2fb6644060a0139256bb7e120d4005efcb1427debbec495c97205719614361c73ab
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,6 +1,17 @@
|
|
1
|
+
inherit_mode:
|
2
|
+
merge:
|
3
|
+
- Exclude
|
4
|
+
|
1
5
|
AllCops:
|
2
6
|
Exclude:
|
3
7
|
- 'gemfiles/**/*'
|
8
|
+
- 'vendor/bundle/'
|
9
|
+
|
10
|
+
Layout/MultilineMethodCallIndentation:
|
11
|
+
EnforcedStyle: indented
|
12
|
+
|
13
|
+
Metrics/ClassLength:
|
14
|
+
Enabled: false
|
4
15
|
|
5
16
|
Naming/MethodName:
|
6
17
|
Enabled: false
|
@@ -35,11 +46,18 @@ Style/TrailingCommaInHashLiteral:
|
|
35
46
|
Style/NumericPredicate:
|
36
47
|
Enabled: false
|
37
48
|
|
49
|
+
Style/CommentedKeyword:
|
50
|
+
Enabled: false
|
51
|
+
|
52
|
+
Layout/IndentHeredoc:
|
53
|
+
Enabled: false
|
54
|
+
|
38
55
|
Metrics/BlockLength:
|
39
56
|
Exclude:
|
40
57
|
- spec/**/*
|
41
58
|
- Rakefile
|
42
59
|
- fear.gemspec
|
60
|
+
- examples/**/*
|
43
61
|
|
44
62
|
Metrics/LineLength:
|
45
63
|
Max: 120
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,17 @@
|
|
1
|
+
## 0.x
|
2
|
+
|
3
|
+
* Rename `Fear::Done` to `Fear::Unit` ([@bolshakov][])
|
4
|
+
* Don't treat symbols as procs while pattern matching. See [#46](https://github.com/bolshakov/fear/pull/46) for motivation ([@bolshakov][])
|
5
|
+
* Revert commit removing `Fear::Future`. Now you can use `Fear.future` again ([@bolshakov][])
|
6
|
+
* Signatures of `Try#recover` and `Try#recover_with` changed. No it pattern match against container
|
7
|
+
see https://github.com/bolshakov/fear/issues/41 for details . ([@bolshakov][])
|
8
|
+
* Add `#xcase` method to extract patterns ([@bolshakov][])
|
9
|
+
* Add `Fear.option`, `Fear.some`, `Fear.none`, `Fear.try`, `Fear.left`, `Fear.right`, and `Fear.for` alternatives to
|
10
|
+
including mixins. ([@bolshakov][])
|
11
|
+
|
1
12
|
## 0.11.0
|
2
13
|
|
3
|
-
* Implement pattern matching and partial functions. See [README](https://github.com/bolshakov/fear#pattern-matching-api-documentation) (
|
14
|
+
* Implement pattern matching and partial functions. See [README](https://github.com/bolshakov/fear#pattern-matching-api-documentation) ([@bolshakov][])
|
4
15
|
* `#to_a` method removed ([@bolshakov][])
|
5
16
|
* `For` syntax changed. See [diff](https://github.com/bolshakov/fear/pull/22/files#diff-04c6e90faac2675aa89e2176d2eec7d8) ([@bolshakov][])
|
6
17
|
* `Fear::None` is singleton object now and could not be instantiated ([@bolshakov][])
|
data/Gemfile
CHANGED
@@ -6,28 +6,35 @@ GIT
|
|
6
6
|
any (= 0.1.0)
|
7
7
|
|
8
8
|
PATH
|
9
|
-
remote:
|
9
|
+
remote: .
|
10
10
|
specs:
|
11
|
-
fear (0.
|
12
|
-
|
11
|
+
fear (1.0.0)
|
12
|
+
lru_redux
|
13
|
+
treetop
|
13
14
|
|
14
15
|
GEM
|
15
16
|
remote: https://rubygems.org/
|
16
17
|
specs:
|
17
18
|
any (0.1.0)
|
18
|
-
appraisal (2.2.0)
|
19
|
-
bundler
|
20
|
-
rake
|
21
|
-
thor (>= 0.14.0)
|
22
19
|
ast (2.4.0)
|
23
20
|
benchmark-ips (2.7.2)
|
21
|
+
concurrent-ruby (1.1.5)
|
24
22
|
diff-lcs (1.3)
|
25
|
-
dry-
|
23
|
+
dry-core (0.4.7)
|
24
|
+
concurrent-ruby (~> 1.0)
|
25
|
+
dry-equalizer (0.2.2)
|
26
26
|
dry-matcher (0.7.0)
|
27
|
+
dry-monads (1.2.0)
|
28
|
+
concurrent-ruby (~> 1.0)
|
29
|
+
dry-core (~> 0.4, >= 0.4.4)
|
30
|
+
dry-equalizer
|
31
|
+
irb (1.0.0)
|
27
32
|
jaro_winkler (1.5.2)
|
33
|
+
lru_redux (1.1.0)
|
28
34
|
parallel (1.14.0)
|
29
35
|
parser (2.6.0.0)
|
30
36
|
ast (~> 2.4.0)
|
37
|
+
polyglot (0.3.5)
|
31
38
|
powerpack (0.1.2)
|
32
39
|
psych (3.1.0)
|
33
40
|
rainbow (3.0.0)
|
@@ -57,7 +64,8 @@ GEM
|
|
57
64
|
rubocop-rspec (1.32.0)
|
58
65
|
rubocop (>= 0.60.0)
|
59
66
|
ruby-progressbar (1.10.0)
|
60
|
-
|
67
|
+
treetop (1.6.10)
|
68
|
+
polyglot (~> 0.3)
|
61
69
|
unicode-display_width (1.4.1)
|
62
70
|
yard (0.9.18)
|
63
71
|
|
@@ -65,12 +73,13 @@ PLATFORMS
|
|
65
73
|
ruby
|
66
74
|
|
67
75
|
DEPENDENCIES
|
68
|
-
appraisal
|
69
76
|
benchmark-ips
|
70
77
|
bundler
|
71
|
-
|
78
|
+
concurrent-ruby
|
72
79
|
dry-matcher
|
80
|
+
dry-monads
|
73
81
|
fear!
|
82
|
+
irb
|
74
83
|
qo!
|
75
84
|
rake (~> 10.0)
|
76
85
|
rspec (~> 3.1)
|
@@ -79,4 +88,4 @@ DEPENDENCIES
|
|
79
88
|
yard
|
80
89
|
|
81
90
|
BUNDLED WITH
|
82
|
-
1.
|
91
|
+
1.17.2
|
data/README.md
CHANGED
@@ -26,10 +26,11 @@ Or install it yourself as:
|
|
26
26
|
* [Option](#option-documentation)
|
27
27
|
* [Try](#try-documentation)
|
28
28
|
* [Either](#either-documentation)
|
29
|
+
* [Future](#future-documentation)
|
29
30
|
* [For composition](#for-composition)
|
30
31
|
* [Pattern Matching](#pattern-matching-api-documentation)
|
31
32
|
|
32
|
-
### Option ([Documentation](
|
33
|
+
### Option ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Option))
|
33
34
|
|
34
35
|
Represents optional (nullable) values. Instances of `Option` are either an instance of
|
35
36
|
`Some` or the object `None`.
|
@@ -37,7 +38,7 @@ Represents optional (nullable) values. Instances of `Option` are either an insta
|
|
37
38
|
The most idiomatic way to use an `Option` instance is to treat it as a collection
|
38
39
|
|
39
40
|
```ruby
|
40
|
-
name =
|
41
|
+
name = Fear.option(params[:name])
|
41
42
|
upper = name.map(&:strip).select { |n| n.length != 0 }.map(&:upcase)
|
42
43
|
puts upper.get_or_else('')
|
43
44
|
```
|
@@ -48,7 +49,7 @@ having to check for the existence of a value.
|
|
48
49
|
A less-idiomatic way to use `Option` values is via pattern matching
|
49
50
|
|
50
51
|
```ruby
|
51
|
-
|
52
|
+
Fear.option(params[:name]).match do |m|
|
52
53
|
m.some { |name| name.strip.upcase }
|
53
54
|
m.none { 'No name value' }
|
54
55
|
end
|
@@ -57,7 +58,7 @@ end
|
|
57
58
|
or manually checking for non emptiness
|
58
59
|
|
59
60
|
```ruby
|
60
|
-
name =
|
61
|
+
name = Fear.option(params[:name])
|
61
62
|
if name.empty?
|
62
63
|
puts 'No name value'
|
63
64
|
else
|
@@ -65,16 +66,29 @@ else
|
|
65
66
|
end
|
66
67
|
```
|
67
68
|
|
69
|
+
Alternatively, include `Fear::Option::Mixin` to use `Option()`, `Some()` and `None()` methods:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
include Fear::Option::Mixin
|
73
|
+
|
74
|
+
Option(42) #=> #<Fear::Some get=42>
|
75
|
+
Option(nil) #=> #<Fear::None>
|
76
|
+
|
77
|
+
Some(42) #=> #<Fear::Some get=42>
|
78
|
+
Some(nil) #=> #<Fear::Some get=nil>
|
79
|
+
None() #=> #<Fear::None>
|
80
|
+
```
|
81
|
+
|
68
82
|
#### Option#get_or_else
|
69
83
|
|
70
84
|
Returns the value from this `Some` or evaluates the given default argument if this is a `None`.
|
71
85
|
|
72
86
|
```ruby
|
73
|
-
|
74
|
-
|
87
|
+
Fear.some(42).get_or_else { 24/2 } #=> 42
|
88
|
+
Fear.none.get_or_else { 24/2 } #=> 12
|
75
89
|
|
76
|
-
|
77
|
-
|
90
|
+
Fear.some(42).get_or_else(12) #=> 42
|
91
|
+
Fear.none.get_or_else(12) #=> 12
|
78
92
|
```
|
79
93
|
|
80
94
|
#### Option#or_else
|
@@ -82,9 +96,9 @@ None.get_or_else(12) #=> 12
|
|
82
96
|
returns self `Some` or the given alternative if this is a `None`.
|
83
97
|
|
84
98
|
```ruby
|
85
|
-
|
86
|
-
|
87
|
-
|
99
|
+
Fear.some(42).or_else { Fear.some(21) } #=> Fear.some(42)
|
100
|
+
Fear.none.or_else { Fear.some(21) } #=> Fear.some(21)
|
101
|
+
Fear.none.or_else { None } #=> None
|
88
102
|
```
|
89
103
|
|
90
104
|
#### Option#inlude?
|
@@ -92,9 +106,9 @@ None.or_else { None } #=> None
|
|
92
106
|
Checks if `Option` has an element that is equal (as determined by `==`) to given values.
|
93
107
|
|
94
108
|
```ruby
|
95
|
-
|
96
|
-
|
97
|
-
|
109
|
+
Fear.some(17).include?(17) #=> true
|
110
|
+
Fear.some(17).include?(7) #=> false
|
111
|
+
Fear.none.include?(17) #=> false
|
98
112
|
```
|
99
113
|
|
100
114
|
#### Option#each
|
@@ -102,8 +116,8 @@ None.include?(17) #=> false
|
|
102
116
|
Performs the given block if this is a `Some`.
|
103
117
|
|
104
118
|
```ruby
|
105
|
-
|
106
|
-
|
119
|
+
Fear.some(17).each { |value| puts value } #=> prints 17
|
120
|
+
Fear.none.each { |value| puts value } #=> does nothing
|
107
121
|
```
|
108
122
|
|
109
123
|
#### Option#map
|
@@ -111,8 +125,8 @@ None.each { |value| puts value } #=> does nothing
|
|
111
125
|
Maps the given block to the value from this `Some` or returns self if this is a `None`
|
112
126
|
|
113
127
|
```ruby
|
114
|
-
|
115
|
-
|
128
|
+
Fear.some(42).map { |v| v/2 } #=> Fear.some(21)
|
129
|
+
Fear.none.map { |v| v/2 } #=> None
|
116
130
|
```
|
117
131
|
|
118
132
|
#### Option#flat_map
|
@@ -120,8 +134,8 @@ None.map { |v| v/2 } #=> None
|
|
120
134
|
Returns the given block applied to the value from this `Some` or returns self if this is a `None`
|
121
135
|
|
122
136
|
```ruby
|
123
|
-
|
124
|
-
|
137
|
+
Fear.some(42).flat_map { |v| Fear.some(v/2) } #=> Fear.some(21)
|
138
|
+
Fear.none.flat_map { |v| Fear.some(v/2) } #=> None
|
125
139
|
```
|
126
140
|
|
127
141
|
#### Option#any?
|
@@ -129,9 +143,9 @@ None.flat_map { |v| Some(v/2) } #=> None
|
|
129
143
|
Returns `false` if `None` or returns the result of the application of the given predicate to the `Some` value.
|
130
144
|
|
131
145
|
```ruby
|
132
|
-
|
133
|
-
|
134
|
-
|
146
|
+
Fear.some(12).any?( |v| v > 10) #=> true
|
147
|
+
Fear.some(7).any?( |v| v > 10) #=> false
|
148
|
+
Fear.none.any?( |v| v > 10) #=> false
|
135
149
|
```
|
136
150
|
|
137
151
|
#### Option#select
|
@@ -140,9 +154,9 @@ Returns self if it is nonempty and applying the predicate to this `Option`'s val
|
|
140
154
|
return `None`.
|
141
155
|
|
142
156
|
```ruby
|
143
|
-
|
144
|
-
|
145
|
-
|
157
|
+
Fear.some(42).select { |v| v > 40 } #=> Fear.success(21)
|
158
|
+
Fear.some(42).select { |v| v < 40 } #=> None
|
159
|
+
Fear.none.select { |v| v < 40 } #=> None
|
146
160
|
```
|
147
161
|
|
148
162
|
#### Option#reject
|
@@ -150,9 +164,9 @@ None.select { |v| v < 40 } #=> None
|
|
150
164
|
Returns `Some` if applying the predicate to this `Option`'s value returns `false`. Otherwise, return `None`.
|
151
165
|
|
152
166
|
```ruby
|
153
|
-
|
154
|
-
|
155
|
-
|
167
|
+
Fear.some(42).reject { |v| v > 40 } #=> None
|
168
|
+
Fear.some(42).reject { |v| v < 40 } #=> Fear.some(42)
|
169
|
+
Fear.none.reject { |v| v < 40 } #=> None
|
156
170
|
```
|
157
171
|
|
158
172
|
#### Option#get
|
@@ -164,14 +178,14 @@ Not an idiomatic way of using Option at all. Returns values of raise `NoSuchElem
|
|
164
178
|
Returns `true` if the `Option` is `None`, `false` otherwise.
|
165
179
|
|
166
180
|
```ruby
|
167
|
-
|
168
|
-
|
181
|
+
Fear.some(42).empty? #=> false
|
182
|
+
Fear.none.empty? #=> true
|
169
183
|
```
|
170
184
|
|
171
185
|
@see https://github.com/scala/scala/blob/2.11.x/src/library/scala/Option.scala
|
172
186
|
|
173
187
|
|
174
|
-
### Try ([Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Try))
|
188
|
+
### Try ([API Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Try))
|
175
189
|
|
176
190
|
The `Try` represents a computation that may either result
|
177
191
|
in an exception, or return a successfully computed value. Instances of `Try`,
|
@@ -183,10 +197,8 @@ exception-handling in all of the places that an exception
|
|
183
197
|
might occur.
|
184
198
|
|
185
199
|
```ruby
|
186
|
-
|
187
|
-
|
188
|
-
dividend = Try { Integer(params[:dividend]) }
|
189
|
-
divisor = Try { Integer(params[:divisor]) }
|
200
|
+
dividend = Fear.try { Integer(params[:dividend]) }
|
201
|
+
divisor = Fear.try { Integer(params[:divisor]) }
|
190
202
|
problem = dividend.flat_map { |x| divisor.map { |y| x / y } }
|
191
203
|
|
192
204
|
problem.match |m|
|
@@ -218,13 +230,22 @@ type of default behavior in the case of failure.
|
|
218
230
|
*NOTE*: Only non-fatal exceptions are caught by the combinators on `Try`.
|
219
231
|
Serious system errors, on the other hand, will be thrown.
|
220
232
|
|
233
|
+
Alternatively, include `Fear::Try::Mixin` to use `Try()` method:
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
include Fear::Try::Mixin
|
237
|
+
|
238
|
+
Try { 4/0 } #=> #<Fear::Failure exception=...>
|
239
|
+
Try { 4/2 } #=> #<Fear::Success value=2>
|
240
|
+
```
|
241
|
+
|
221
242
|
#### Try#get_or_else
|
222
243
|
|
223
244
|
Returns the value from this `Success` or evaluates the given default argument if this is a `Failure`.
|
224
245
|
|
225
246
|
```ruby
|
226
|
-
|
227
|
-
|
247
|
+
Fear.success(42).get_or_else { 24/2 } #=> 42
|
248
|
+
Fear.failure(ArgumentError.new).get_or_else { 24/2 } #=> 12
|
228
249
|
```
|
229
250
|
|
230
251
|
#### Try#include?
|
@@ -232,9 +253,9 @@ Failure(ArgumentError.new).get_or_else { 24/2 } #=> 12
|
|
232
253
|
Returns `true` if it has an element that is equal given values, `false` otherwise.
|
233
254
|
|
234
255
|
```ruby
|
235
|
-
|
236
|
-
|
237
|
-
|
256
|
+
Fear.success(17).include?(17) #=> true
|
257
|
+
Fear.success(17).include?(7) #=> false
|
258
|
+
Fear.failure(ArgumentError.new).include?(17) #=> false
|
238
259
|
```
|
239
260
|
|
240
261
|
#### Try#each
|
@@ -243,8 +264,8 @@ Performs the given block if this is a `Success`. If block raise an error,
|
|
243
264
|
then this method may raise an exception.
|
244
265
|
|
245
266
|
```ruby
|
246
|
-
|
247
|
-
|
267
|
+
Fear.success(17).each { |value| puts value } #=> prints 17
|
268
|
+
Fear.failure(ArgumentError.new).each { |value| puts value } #=> does nothing
|
248
269
|
```
|
249
270
|
|
250
271
|
#### Try#map
|
@@ -252,8 +273,8 @@ Failure(ArgumentError.new).each { |value| puts value } #=> does nothing
|
|
252
273
|
Maps the given block to the value from this `Success` or returns self if this is a `Failure`.
|
253
274
|
|
254
275
|
```ruby
|
255
|
-
|
256
|
-
|
276
|
+
Fear.success(42).map { |v| v/2 } #=> Fear.success(21)
|
277
|
+
Fear.failure(ArgumentError.new).map { |v| v/2 } #=> Fear.failure(ArgumentError.new)
|
257
278
|
```
|
258
279
|
|
259
280
|
#### Try#flat_map
|
@@ -261,8 +282,8 @@ Failure(ArgumentError.new).map { |v| v/2 } #=> Failure(ArgumentError.new)
|
|
261
282
|
Returns the given block applied to the value from this `Success`or returns self if this is a `Failure`.
|
262
283
|
|
263
284
|
```ruby
|
264
|
-
|
265
|
-
|
285
|
+
Fear.success(42).flat_map { |v| Fear.success(v/2) } #=> Fear.success(21)
|
286
|
+
Fear.failure(ArgumentError.new).flat_map { |v| Fear.success(v/2) } #=> Fear.failure(ArgumentError.new)
|
266
287
|
```
|
267
288
|
|
268
289
|
#### Try#to_option
|
@@ -270,8 +291,8 @@ Failure(ArgumentError.new).flat_map { |v| Success(v/2) } #=> Failure(ArgumentErr
|
|
270
291
|
Returns an `Some` containing the `Success` value or a `None` if this is a `Failure`.
|
271
292
|
|
272
293
|
```ruby
|
273
|
-
|
274
|
-
|
294
|
+
Fear.success(42).to_option #=> Fear.some(21)
|
295
|
+
Fear.failure(ArgumentError.new).to_option #=> None
|
275
296
|
```
|
276
297
|
|
277
298
|
#### Try#any?
|
@@ -279,20 +300,20 @@ Failure(ArgumentError.new).to_option #=> None
|
|
279
300
|
Returns `false` if `Failure` or returns the result of the application of the given predicate to the `Success` value.
|
280
301
|
|
281
302
|
```ruby
|
282
|
-
|
283
|
-
|
284
|
-
|
303
|
+
Fear.success(12).any?( |v| v > 10) #=> true
|
304
|
+
Fear.success(7).any?( |v| v > 10) #=> false
|
305
|
+
Fear.failure(ArgumentError.new).any?( |v| v > 10) #=> false
|
285
306
|
```
|
286
307
|
|
287
308
|
#### Try#success? and Try#failure?
|
288
309
|
|
289
310
|
|
290
311
|
```ruby
|
291
|
-
|
292
|
-
|
312
|
+
Fear.success(12).success? #=> true
|
313
|
+
Fear.success(12).failure? #=> true
|
293
314
|
|
294
|
-
|
295
|
-
|
315
|
+
Fear.failure(ArgumentError.new).success? #=> false
|
316
|
+
Fear.failure(ArgumentError.new).failure? #=> true
|
296
317
|
```
|
297
318
|
|
298
319
|
#### Try#get
|
@@ -300,8 +321,8 @@ Failure(ArgumentError.new).failure? #=> true
|
|
300
321
|
Returns the value from this `Success` or raise the exception if this is a `Failure`.
|
301
322
|
|
302
323
|
```ruby
|
303
|
-
|
304
|
-
|
324
|
+
Fear.success(42).get #=> 42
|
325
|
+
Fear.failure(ArgumentError.new).get #=> ArgumentError: ArgumentError
|
305
326
|
```
|
306
327
|
|
307
328
|
#### Try#or_else
|
@@ -309,9 +330,9 @@ Failure(ArgumentError.new).get #=> ArgumentError: ArgumentError
|
|
309
330
|
Returns self `Try` if it's a `Success` or the given alternative if this is a `Failure`.
|
310
331
|
|
311
332
|
```ruby
|
312
|
-
|
313
|
-
|
314
|
-
|
333
|
+
Fear.success(42).or_else { Fear.success(-1) } #=> Fear.success(42)
|
334
|
+
Fear.failure(ArgumentError.new).or_else { Fear.success(-1) } #=> Fear.success(-1)
|
335
|
+
Fear.failure(ArgumentError.new).or_else { Fear.try { 1/0 } } #=> Fear.failure(ZeroDivisionError.new('divided by 0'))
|
315
336
|
```
|
316
337
|
|
317
338
|
#### Try#flatten
|
@@ -319,10 +340,10 @@ Failure(ArgumentError.new).or_else { Try { 1/0 } } #=> Failure(ZeroDivisionErro
|
|
319
340
|
Transforms a nested `Try`, ie, a `Success` of `Success`, into an un-nested `Try`, ie, a `Success`.
|
320
341
|
|
321
342
|
```ruby
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
343
|
+
Fear.success(42).flatten #=> Fear.success(42)
|
344
|
+
Fear.success(Fear.success(42)).flatten #=> Fear.success(42)
|
345
|
+
Fear.success(Fear.failure(ArgumentError.new)).flatten #=> Fear.failure(ArgumentError.new)
|
346
|
+
Fear.failure(ArgumentError.new).flatten { -1 } #=> Fear.failure(ArgumentError.new)
|
326
347
|
```
|
327
348
|
|
328
349
|
#### Try#select
|
@@ -330,38 +351,58 @@ Failure(ArgumentError.new).flatten { -1 } #=> Failure(ArgumentError.new)
|
|
330
351
|
Converts this to a `Failure` if the predicate is not satisfied.
|
331
352
|
|
332
353
|
```ruby
|
333
|
-
|
334
|
-
#=>
|
335
|
-
|
336
|
-
#=>
|
337
|
-
|
338
|
-
#=>
|
354
|
+
Fear.success(42).select { |v| v > 40 }
|
355
|
+
#=> Fear.success(21)
|
356
|
+
Fear.success(42).select { |v| v < 40 }
|
357
|
+
#=> Fear.failure(Fear::NoSuchElementError.new("Predicate does not hold for 42"))
|
358
|
+
Fear.failure(ArgumentError.new).select { |v| v < 40 }
|
359
|
+
#=> Fear.failure(ArgumentError.new)
|
339
360
|
```
|
340
361
|
|
341
|
-
####
|
362
|
+
#### Recovering from errors
|
342
363
|
|
343
|
-
|
364
|
+
There are two ways to recover from the error. `Try#recover_with` method is like `flat_map` for the exception. And
|
365
|
+
you can pattern match against the error!
|
344
366
|
|
345
367
|
```ruby
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
368
|
+
Fear.success(42).recover_with do |m|
|
369
|
+
m.case(ZeroDivisionError) { Fear.success(0) }
|
370
|
+
end #=> Fear.success(42)
|
371
|
+
|
372
|
+
Fear.failure(ArgumentError.new).recover_with do |m|
|
373
|
+
m.case(ZeroDivisionError) { Fear.success(0) }
|
374
|
+
m.case(ArgumentError) { |error| Fear.success(error.class.name) }
|
375
|
+
end #=> Fear.success('ArgumentError')
|
352
376
|
```
|
353
377
|
|
354
|
-
|
378
|
+
If the block raises error, this new error returned as an result
|
355
379
|
|
356
|
-
|
380
|
+
```ruby
|
381
|
+
Fear.failure(ArgumentError.new).recover_with do
|
382
|
+
raise
|
383
|
+
end #=> Fear.failure(RuntimeError)
|
384
|
+
```
|
385
|
+
|
386
|
+
The second possibility for recovery is `Try#recover` method. It is like `map` for the exception. And it's also heavely
|
387
|
+
relies on pattern matching.
|
357
388
|
|
358
389
|
```ruby
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
390
|
+
Fear.success(42).recover do |m|
|
391
|
+
m.case(&:message)
|
392
|
+
end #=> Fear.success(42)
|
393
|
+
|
394
|
+
Fear.failure(ArgumentError.new).recover do |m|
|
395
|
+
m.case(ZeroDivisionError) { 0 }
|
396
|
+
m.case(&:message)
|
397
|
+
end #=> Fear.success('ArgumentError')
|
398
|
+
```
|
399
|
+
|
400
|
+
If the block raises an error, this new error returned as an result
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
Fear.failure(ArgumentError.new).recover do |m|
|
404
|
+
raise
|
405
|
+
end #=> Fear.failure(RuntimeError)
|
365
406
|
```
|
366
407
|
|
367
408
|
#### Try#to_either
|
@@ -369,11 +410,11 @@ Failure(ArgumentError.new).recover { |e| raise }
|
|
369
410
|
Returns `Left` with exception if this is a `Failure`, otherwise returns `Right` with `Success` value.
|
370
411
|
|
371
412
|
```ruby
|
372
|
-
|
373
|
-
|
413
|
+
Fear.success(42).to_either #=> Fear.right(42)
|
414
|
+
Fear.failure(ArgumentError.new).to_either #=> Fear.left(ArgumentError.new)
|
374
415
|
```
|
375
416
|
|
376
|
-
### Either ([Documentation](
|
417
|
+
### Either ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Option))
|
377
418
|
|
378
419
|
Represents a value of one of two possible types (a disjoint union.)
|
379
420
|
An instance of `Either` is either an instance of `Left` or `Right`.
|
@@ -390,9 +431,9 @@ received input is a +String+ or an +Fixnum+.
|
|
390
431
|
```ruby
|
391
432
|
in = Readline.readline('Type Either a string or an Int: ', true)
|
392
433
|
result = begin
|
393
|
-
|
434
|
+
Fear.right(Integer(in))
|
394
435
|
rescue ArgumentError
|
395
|
-
|
436
|
+
Fear.left(in)
|
396
437
|
end
|
397
438
|
|
398
439
|
result.match do |m|
|
@@ -410,16 +451,25 @@ Either is right-biased, which means that `Right` is assumed to be the default ca
|
|
410
451
|
operate on. If it is `Left`, operations like `#map`, `#flat_map`, ... return the `Left` value
|
411
452
|
unchanged.
|
412
453
|
|
454
|
+
Alternatively, include `Fear::Either::Mixin` to use `Left()`, and `Right()` methods:
|
455
|
+
|
456
|
+
```ruby
|
457
|
+
include Fear::Either::Mixin
|
458
|
+
|
459
|
+
Left(42) #=> #<Fear::Left value=42>
|
460
|
+
Right(42) #=> #<Fear::Right value=42>
|
461
|
+
```
|
462
|
+
|
413
463
|
#### Either#get_or_else
|
414
464
|
|
415
465
|
Returns the value from this `Right` or evaluates the given default argument if this is a `Left`.
|
416
466
|
|
417
467
|
```ruby
|
418
|
-
|
419
|
-
|
468
|
+
Fear.right(42).get_or_else { 24/2 } #=> 42
|
469
|
+
Fear.left('undefined').get_or_else { 24/2 } #=> 12
|
420
470
|
|
421
|
-
|
422
|
-
|
471
|
+
Fear.right(42).get_or_else(12) #=> 42
|
472
|
+
Fear.left('undefined').get_or_else(12) #=> 12
|
423
473
|
```
|
424
474
|
|
425
475
|
#### Either#or_else
|
@@ -427,9 +477,9 @@ Left('undefined').get_or_else(12) #=> 12
|
|
427
477
|
Returns self `Right` or the given alternative if this is a `Left`.
|
428
478
|
|
429
479
|
```ruby
|
430
|
-
|
431
|
-
|
432
|
-
|
480
|
+
Fear.right(42).or_else { Fear.right(21) } #=> Fear.right(42)
|
481
|
+
Fear.left('unknown').or_else { Fear.right(21) } #=> Fear.right(21)
|
482
|
+
Fear.left('unknown').or_else { Fear.left('empty') } #=> Fear.left('empty')
|
433
483
|
```
|
434
484
|
|
435
485
|
#### Either#include?
|
@@ -437,9 +487,9 @@ Left('unknown').or_else { Left('empty') } #=> Left('empty')
|
|
437
487
|
Returns `true` if `Right` has an element that is equal to given value, `false` otherwise.
|
438
488
|
|
439
489
|
```ruby
|
440
|
-
|
441
|
-
|
442
|
-
|
490
|
+
Fear.right(17).include?(17) #=> true
|
491
|
+
Fear.right(17).include?(7) #=> false
|
492
|
+
Fear.left('undefined').include?(17) #=> false
|
443
493
|
```
|
444
494
|
|
445
495
|
#### Either#each
|
@@ -447,8 +497,8 @@ Left('undefined').include?(17) #=> false
|
|
447
497
|
Performs the given block if this is a `Right`.
|
448
498
|
|
449
499
|
```ruby
|
450
|
-
|
451
|
-
|
500
|
+
Fear.right(17).each { |value| puts value } #=> prints 17
|
501
|
+
Fear.left('undefined').each { |value| puts value } #=> does nothing
|
452
502
|
```
|
453
503
|
|
454
504
|
#### Either#map
|
@@ -456,8 +506,8 @@ Left('undefined').each { |value| puts value } #=> does nothing
|
|
456
506
|
Maps the given block to the value from this `Right` or returns self if this is a `Left`.
|
457
507
|
|
458
508
|
```ruby
|
459
|
-
|
460
|
-
|
509
|
+
Fear.right(42).map { |v| v/2 } #=> Fear.right(21)
|
510
|
+
Fear.left('undefined').map { |v| v/2 } #=> Fear.left('undefined')
|
461
511
|
```
|
462
512
|
|
463
513
|
#### Either#flat_map
|
@@ -465,8 +515,8 @@ Left('undefined').map { |v| v/2 } #=> Left('undefined')
|
|
465
515
|
Returns the given block applied to the value from this `Right` or returns self if this is a `Left`.
|
466
516
|
|
467
517
|
```ruby
|
468
|
-
|
469
|
-
|
518
|
+
Fear.right(42).flat_map { |v| Fear.right(v/2) } #=> Fear.right(21)
|
519
|
+
Fear.left('undefined').flat_map { |v| Fear.right(v/2) } #=> Fear.left('undefined')
|
470
520
|
```
|
471
521
|
|
472
522
|
#### Either#to_option
|
@@ -474,8 +524,8 @@ Left('undefined').flat_map { |v| Right(v/2) } #=> Left('undefined')
|
|
474
524
|
Returns an `Some` containing the `Right` value or a `None` if this is a `Left`.
|
475
525
|
|
476
526
|
```ruby
|
477
|
-
|
478
|
-
|
527
|
+
Fear.right(42).to_option #=> Fear.some(21)
|
528
|
+
Fear.left('undefined').to_option #=> Fear::None
|
479
529
|
```
|
480
530
|
|
481
531
|
#### Either#any?
|
@@ -483,9 +533,9 @@ Left('undefined').to_option #=> None
|
|
483
533
|
Returns `false` if `Left` or returns the result of the application of the given predicate to the `Right` value.
|
484
534
|
|
485
535
|
```ruby
|
486
|
-
|
487
|
-
|
488
|
-
|
536
|
+
Fear.right(12).any?( |v| v > 10) #=> true
|
537
|
+
Fear.right(7).any?( |v| v > 10) #=> false
|
538
|
+
Fear.left('undefined').any?( |v| v > 10) #=> false
|
489
539
|
```
|
490
540
|
|
491
541
|
#### Either#right?, Either#success?
|
@@ -493,8 +543,8 @@ Left('undefined').any?( |v| v > 10) #=> false
|
|
493
543
|
Returns `true` if this is a `Right`, `false` otherwise.
|
494
544
|
|
495
545
|
```ruby
|
496
|
-
|
497
|
-
|
546
|
+
Fear.right(42).right? #=> true
|
547
|
+
Fear.left('err').right? #=> false
|
498
548
|
```
|
499
549
|
|
500
550
|
#### Either#left?, Either#failure?
|
@@ -502,8 +552,8 @@ Left('err').right? #=> false
|
|
502
552
|
Returns `true` if this is a `Left`, `false` otherwise.
|
503
553
|
|
504
554
|
```ruby
|
505
|
-
|
506
|
-
|
555
|
+
Fear.right(42).left? #=> false
|
556
|
+
Fear.left('err').left? #=> true
|
507
557
|
```
|
508
558
|
|
509
559
|
#### Either#select_or_else
|
@@ -512,10 +562,10 @@ Returns `Left` of the default if the given predicate does not hold for the right
|
|
512
562
|
returns `Right`.
|
513
563
|
|
514
564
|
```ruby
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
565
|
+
Fear.right(12).select_or_else(-1, &:even?) #=> Fear.right(12)
|
566
|
+
Fear.right(7).select_or_else(-1, &:even?) #=> Fear.left(-1)
|
567
|
+
Fear.left(12).select_or_else(-1, &:even?) #=> Fear.left(12)
|
568
|
+
Fear.left(12).select_or_else(-> { -1 }, &:even?) #=> Fear.left(12)
|
519
569
|
```
|
520
570
|
|
521
571
|
#### Either#select
|
@@ -523,10 +573,10 @@ Left(12).select_or_else(-> { -1 }, &:even?) #=> Left(12)
|
|
523
573
|
Returns `Left` of value if the given predicate does not hold for the right value, otherwise, returns `Right`.
|
524
574
|
|
525
575
|
```ruby
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
576
|
+
Fear.right(12).select(&:even?) #=> Fear.right(12)
|
577
|
+
Fear.right(7).select(&:even?) #=> Fear.left(7)
|
578
|
+
Fear.left(12).select(&:even?) #=> Fear.left(12)
|
579
|
+
Fear.left(7).select(&:even?) #=> Fear.left(7)
|
530
580
|
```
|
531
581
|
|
532
582
|
#### Either#reject
|
@@ -534,10 +584,10 @@ Left(7).select(&:even?) #=> Left(7)
|
|
534
584
|
Returns `Left` of value if the given predicate holds for the right value, otherwise, returns `Right`.
|
535
585
|
|
536
586
|
```ruby
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
587
|
+
Fear.right(12).reject(&:even?) #=> Fear.left(12)
|
588
|
+
Fear.right(7).reject(&:even?) #=> Fear.right(7)
|
589
|
+
Fear.left(12).reject(&:even?) #=> Fear.left(12)
|
590
|
+
Fear.left(7).reject(&:even?) #=> Fear.left(7)
|
541
591
|
```
|
542
592
|
|
543
593
|
#### Either#swap
|
@@ -545,8 +595,8 @@ Left(7).reject(&:even?) #=> Left(7)
|
|
545
595
|
If this is a `Left`, then return the left value in `Right` or vice versa.
|
546
596
|
|
547
597
|
```ruby
|
548
|
-
|
549
|
-
|
598
|
+
Fear.left('left').swap #=> Fear.right('left')
|
599
|
+
Fear.right('right').swap #=> Fear.left('left')
|
550
600
|
```
|
551
601
|
|
552
602
|
#### Either#reduce
|
@@ -569,10 +619,10 @@ Joins an `Either` through `Right`. This method requires that the right side of t
|
|
569
619
|
`Either` type. This method, and `join_left`, are analogous to `Option#flatten`
|
570
620
|
|
571
621
|
```ruby
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
622
|
+
Fear.right(Fear.right(12)).join_right #=> Fear.right(12)
|
623
|
+
Fear.right(Fear.left("flower")).join_right #=> Fear.left("flower")
|
624
|
+
Fear.left("flower").join_right #=> Fear.left("flower")
|
625
|
+
Fear.left(Fear.right("flower")).join_right #=> Fear.left(Fear.right("flower"))
|
576
626
|
```
|
577
627
|
|
578
628
|
#### Either#join_right
|
@@ -581,32 +631,179 @@ Joins an `Either` through `Left`. This method requires that the left side of thi
|
|
581
631
|
`Either` type. This method, and `join_right`, are analogous to `Option#flatten`
|
582
632
|
|
583
633
|
```ruby
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
634
|
+
Fear.left(Fear.right("flower")).join_left #=> Fear.right("flower")
|
635
|
+
Fear.left(Fear.left(12)).join_left #=> Fear.left(12)
|
636
|
+
Fear.right("daisy").join_left #=> Fear.right("daisy")
|
637
|
+
Fear.right(Fear.left("daisy")).join_left #=> Fear.right(Fear.left("daisy"))
|
588
638
|
```
|
589
|
-
|
590
|
-
###
|
639
|
+
|
640
|
+
### Future ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Future))
|
641
|
+
|
642
|
+
Asynchronous computations that yield futures are created
|
643
|
+
with the `Fear.future` call
|
644
|
+
|
645
|
+
```ruby
|
646
|
+
success = "Hello"
|
647
|
+
f = Fear.future { success + ' future!' }
|
648
|
+
f.on_success do |result|
|
649
|
+
puts result
|
650
|
+
end
|
651
|
+
```
|
652
|
+
|
653
|
+
Multiple callbacks may be registered; there is no guarantee
|
654
|
+
that they will be executed in a particular order.
|
655
|
+
|
656
|
+
The future may contain an exception and this means
|
657
|
+
that the future failed. Futures obtained through combinators
|
658
|
+
have the same error as the future they were obtained from.
|
659
|
+
|
660
|
+
```ruby
|
661
|
+
f = Fear.future { 5 }
|
662
|
+
g = Fear.future { 3 }
|
663
|
+
|
664
|
+
f.flat_map do |x|
|
665
|
+
g.map { |y| x + y }
|
666
|
+
end
|
667
|
+
```
|
668
|
+
|
669
|
+
Futures use [Concurrent::Promise](https://ruby-concurrency.github.io/concurrent-ruby/1.1.5/Concurrent/Promise.html#constructor_details)
|
670
|
+
under the hood. `Fear.future` accepts optional configuration Hash passed directly to underlying promise. For example,
|
671
|
+
run it on custom thread pool.
|
672
|
+
|
673
|
+
```ruby
|
674
|
+
require 'open-uri'
|
675
|
+
pool = Concurrent::FixedThreadPool.new(5)
|
676
|
+
future = Fear.future(executor: pool) { open('https://example.com/') }
|
677
|
+
future.map(&:read).each do |body|
|
678
|
+
puts "#{body}"
|
679
|
+
end
|
680
|
+
|
681
|
+
```
|
682
|
+
|
683
|
+
Futures support common monadic operations -- `#map`, `#flat_map`, and `#each`. That's why it's possible to combine them
|
684
|
+
using `Fear.for`, It returns the Future containing Success of `5 + 3` eventually.
|
685
|
+
|
686
|
+
```ruby
|
687
|
+
f = Fear.future { 5 }
|
688
|
+
g = Fear.future { 3 }
|
689
|
+
|
690
|
+
Fear.for(f, g) do |x, y|
|
691
|
+
x + y
|
692
|
+
end
|
693
|
+
```
|
694
|
+
|
695
|
+
Future goes with the number of callbacks. You can register several callbacks, but the order of execution isn't guaranteed
|
696
|
+
|
697
|
+
```ruby
|
698
|
+
f = Fear.future { ... } # call external service
|
699
|
+
f.on_success do |result|
|
700
|
+
# handle service response
|
701
|
+
end
|
702
|
+
|
703
|
+
f.on_failure do |error|
|
704
|
+
# handle exception
|
705
|
+
end
|
706
|
+
```
|
707
|
+
|
708
|
+
or you can wait for Future completion
|
709
|
+
|
710
|
+
```ruby
|
711
|
+
f.on_complete do |result|
|
712
|
+
result.match do |m|
|
713
|
+
m.success { |value| ... }
|
714
|
+
m.failure { |error| ... }
|
715
|
+
end
|
716
|
+
end
|
717
|
+
```
|
718
|
+
|
719
|
+
In sake of convenience `#on_success` callback aliased as `#each`.
|
720
|
+
|
721
|
+
It's possible to get future value directly, but since it may be incomplete, `#value` method returns `Fear::Option`. So,
|
722
|
+
there are three possible responses:
|
723
|
+
|
724
|
+
```ruby
|
725
|
+
future.value #=>
|
726
|
+
# Fear::Some<Fear::Success> #=> future completed with value
|
727
|
+
# Fear::Some<Fear::Failure> #=> future completed with error
|
728
|
+
# Fear::None #=> future not yet completed
|
729
|
+
```
|
730
|
+
|
731
|
+
There is a variety of methods to manipulate with futures.
|
732
|
+
|
733
|
+
```ruby
|
734
|
+
Fear.future { open('http://example.com').read }
|
735
|
+
.transform(
|
736
|
+
->(value) { ... },
|
737
|
+
->(error) { ... },
|
738
|
+
)
|
739
|
+
|
740
|
+
future = Fear.future { 5 }
|
741
|
+
future.select(&:odd?) # evaluates to Fear.success(5)
|
742
|
+
future.select(&:even?) # evaluates to Fear.error(NoSuchElementError)
|
743
|
+
```
|
744
|
+
|
745
|
+
You can zip several asynchronous computations into one future. For you can call two external services and
|
746
|
+
then zip the results into one future containing array of both responses:
|
747
|
+
|
748
|
+
```ruby
|
749
|
+
future1 = Fear.future { call_service1 }
|
750
|
+
future1 = Fear.future { call_service2 }
|
751
|
+
future1.zip(future2)
|
752
|
+
```
|
753
|
+
|
754
|
+
It returns the same result as `Fear.future { [call_service1, call_service2] }`, but the first version performs
|
755
|
+
two simultaneous calls.
|
756
|
+
|
757
|
+
There are two ways to recover from failure. `Future#recover` is live `#map` for failures:
|
758
|
+
|
759
|
+
```ruby
|
760
|
+
Fear.future { 2 / 0 }.recover do |m|
|
761
|
+
m.case(ZeroDivisionError) { 0 }
|
762
|
+
end #=> returns new future of Fear.success(0)
|
763
|
+
```
|
764
|
+
|
765
|
+
If the future resolved to success or recovery matcher did not matched, it returns the future `Fear::Failure`.
|
766
|
+
|
767
|
+
The second option is `Future#fallbock_to` method. It allows to fallback to result of another future in case of failure
|
768
|
+
|
769
|
+
```ruby
|
770
|
+
future = Fear.future { fail 'error' }
|
771
|
+
fallback = Fear.future { 5 }
|
772
|
+
future.fallback_to(fallback) # evaluates to 5
|
773
|
+
```
|
774
|
+
|
775
|
+
You can run callbacks in specific order using `#and_then` method:
|
776
|
+
|
777
|
+
```ruby
|
778
|
+
f = Fear.future { 5 }
|
779
|
+
f.and_then do
|
780
|
+
fail 'runtime error'
|
781
|
+
end.and_then do |m|
|
782
|
+
m.success { |value| puts value } # it evaluates this branch
|
783
|
+
m.failure { |error| puts error.massage }
|
784
|
+
end
|
785
|
+
```
|
786
|
+
|
787
|
+
### For composition ([API Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/ForApi))
|
591
788
|
|
592
789
|
Provides syntactic sugar for composition of multiple monadic operations.
|
593
790
|
It supports two such operations - `flat_map` and `map`. Any class providing them
|
594
791
|
is supported by `For`.
|
595
792
|
|
596
793
|
```ruby
|
597
|
-
|
794
|
+
Fear.for(Fear.some(2), Fear.some(3)) do |a, b|
|
598
795
|
a * b
|
599
|
-
end #=>
|
796
|
+
end #=> Fear.some(6)
|
600
797
|
```
|
601
798
|
|
602
799
|
If one of operands is None, the result is None
|
603
800
|
|
604
801
|
```ruby
|
605
|
-
|
802
|
+
Fear.for(Fear.some(2), None) do |a, b|
|
606
803
|
a * b
|
607
804
|
end #=> None
|
608
805
|
|
609
|
-
|
806
|
+
Fear.for(None, Fear.some(2)) do |a, b|
|
610
807
|
a * b
|
611
808
|
end #=> None
|
612
809
|
```
|
@@ -614,7 +811,7 @@ end #=> None
|
|
614
811
|
Lets look at first example:
|
615
812
|
|
616
813
|
```ruby
|
617
|
-
|
814
|
+
Fear.for(Fear.some(2), None) do |a, b|
|
618
815
|
a * b
|
619
816
|
end #=> None
|
620
817
|
```
|
@@ -622,8 +819,8 @@ end #=> None
|
|
622
819
|
it is translated to:
|
623
820
|
|
624
821
|
```ruby
|
625
|
-
|
626
|
-
|
822
|
+
Fear.some(2).flat_map do |a|
|
823
|
+
Fear.some(3).map do |b|
|
627
824
|
a * b
|
628
825
|
end
|
629
826
|
end
|
@@ -632,7 +829,7 @@ end
|
|
632
829
|
It works with arrays as well
|
633
830
|
|
634
831
|
```ruby
|
635
|
-
|
832
|
+
Fear.for([1, 2], [2, 3], [3, 4]) { |a, b, c| a * b * c }
|
636
833
|
#=> [6, 8, 9, 12, 12, 16, 18, 24]
|
637
834
|
|
638
835
|
```
|
@@ -653,7 +850,7 @@ If you pass lambda as a variable value, it would be evaluated
|
|
653
850
|
only on demand.
|
654
851
|
|
655
852
|
```ruby
|
656
|
-
|
853
|
+
Fear.for(proc { None }, proc { raise 'kaboom' } ) do |a, b|
|
657
854
|
a * b
|
658
855
|
end #=> None
|
659
856
|
```
|
@@ -664,131 +861,222 @@ You can refer to previously defined variables from within lambdas.
|
|
664
861
|
```ruby
|
665
862
|
maybe_user = find_user('Paul') #=> <#Option value=<#User ...>>
|
666
863
|
|
667
|
-
|
864
|
+
Fear.for(maybe_user, ->(user) { user.birthday }) do |user, birthday|
|
668
865
|
"#{user.name} was born on #{birthday}"
|
669
|
-
end #=>
|
866
|
+
end #=> Fear.some('Paul was born on 1987-06-17')
|
670
867
|
```
|
671
868
|
|
672
|
-
### Pattern Matching (API Documentation)
|
869
|
+
### Pattern Matching ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/PatternMatchingApi))
|
673
870
|
|
674
|
-
|
675
|
-
defined on domain described with guard.
|
871
|
+
#### Syntax
|
676
872
|
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
873
|
+
To pattern match against a value, use `Fear.match` function, and provide at least one case clause:
|
874
|
+
|
875
|
+
```ruby
|
876
|
+
x = Random.rand(10)
|
877
|
+
|
878
|
+
Fear.match(x) do |m|
|
879
|
+
m.case(0) { 'zero' }
|
880
|
+
m.case(1) { 'one' }
|
881
|
+
m.case(2) { 'two' }
|
882
|
+
m.else { 'many' }
|
883
|
+
end
|
686
884
|
```
|
687
885
|
|
688
|
-
|
886
|
+
The `x` above is a random integer from 0 to 10. The last clause `else` is a “catch all” case
|
887
|
+
for anything other than `0`, `1`, and `2`. If you want to ensure that an Integer value is passed,
|
888
|
+
matching against type available:
|
689
889
|
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
890
|
+
```ruby
|
891
|
+
Fear.match(x) do |m|
|
892
|
+
m.case(Integer, 0) { 'zero' }
|
893
|
+
m.case(Integer, 1) { 'one' }
|
894
|
+
m.case(Integer, 2) { 'two' }
|
895
|
+
m.case(Integer) { 'many' }
|
896
|
+
end
|
897
|
+
```
|
697
898
|
|
698
|
-
|
699
|
-
is_even = Fear.case(->(arg) { arg % 2 == 0}) { |arg| "#{arg} is even" }
|
700
|
-
is_odd = Fear.case(->(arg) { arg % 2 == 1}) { |arg| "#{arg} is odd" }
|
899
|
+
Providing something other than Integer will raise `Fear::MatchError` error.
|
701
900
|
|
702
|
-
|
901
|
+
#### Pattern guards
|
703
902
|
|
704
|
-
|
705
|
-
integer_two_times = Fear.case(Integer) { |x| x * 2 }
|
903
|
+
You can use whatever you want as a pattern guard, if it respond to `#===` method to to make cases more specific.
|
706
904
|
|
707
|
-
|
708
|
-
|
709
|
-
|
905
|
+
```ruby
|
906
|
+
m.case(20..40) { |m| "#{m} is within range" }
|
907
|
+
m.case(->(x) { x > 10}) { |m| "#{m} is greater than 10" }
|
908
|
+
m.case(:even?.to_proc) { |x| "#{x} is even" }
|
909
|
+
m.case(:odd?.to_proc) { |x| "#{x} is odd" }
|
710
910
|
```
|
711
911
|
|
712
|
-
|
713
|
-
branches. For instance this matcher applies different functions to Integers and Strings
|
912
|
+
It's also possible to create matcher and use it several times:
|
714
913
|
|
715
|
-
```ruby
|
716
|
-
Fear.
|
914
|
+
```ruby
|
915
|
+
matcher = Fear.matcher do |m|
|
717
916
|
m.case(Integer) { |n| "#{n} is a number" }
|
718
917
|
m.case(String) { |n| "#{n} is a string" }
|
918
|
+
m.else { |n| "#{n} is a #{n.class}" }
|
919
|
+
end
|
920
|
+
|
921
|
+
matcher.(42) #=> "42 is a number"
|
922
|
+
matcher.(10..20) #=> "10..20 is a Range"
|
923
|
+
```
|
924
|
+
|
925
|
+
#### Pattern extraction
|
926
|
+
|
927
|
+
It's possible to use special syntax to match against an object and extract a variable form this object.
|
928
|
+
To perform such extraction, `#xcase` method should be used. The following example should give you a sense
|
929
|
+
how extraction works.
|
930
|
+
|
931
|
+
```ruby
|
932
|
+
matcher = Fear.matcher do |m|
|
933
|
+
m.xcase('[1, *tail]') { |tail:| tail }
|
719
934
|
end
|
720
935
|
```
|
721
936
|
|
722
|
-
|
723
|
-
|
724
|
-
|
937
|
+
It matches only on an array starting from `1` integer, and captures its tail:
|
938
|
+
|
939
|
+
```ruby
|
940
|
+
matcher.([1,2,3]) #=> [2,3]
|
941
|
+
matcher.([2,3]) #=> raises MatchError
|
942
|
+
```
|
943
|
+
|
944
|
+
If you want to match against any value, use `_`
|
725
945
|
|
726
946
|
```ruby
|
727
|
-
Fear.
|
728
|
-
m.
|
729
|
-
|
730
|
-
m.else { |n| "#{n} is a #{n.class}" }
|
731
|
-
end #=> "10..20 is a Range"
|
947
|
+
matcher = Fear.matcher do |m|
|
948
|
+
m.xcase('[1, _, 3]') { .. }
|
949
|
+
end
|
732
950
|
```
|
733
951
|
|
734
|
-
|
952
|
+
It matches against `[1, 2, 3]`, `[1, 'foo', 3]`, but not `[1, 2]`. It's also possible to capture several variables
|
953
|
+
at the same time. Tho following example describes an array starting from `1`, and captures second and third elements.
|
954
|
+
|
955
|
+
|
956
|
+
```ruby
|
957
|
+
matcher = Fear.matcher do |m|
|
958
|
+
m.xcase('[1, second, third]') { |second:, third: |.. }
|
959
|
+
end
|
960
|
+
```
|
961
|
+
|
962
|
+
Matching on deeper structures is possible as well:
|
963
|
+
|
964
|
+
```ruby
|
965
|
+
matcher = Fear.matcher do |m|
|
966
|
+
m.xcase('[["status", first_status], 4, *tail]') { |first_status:, tail: |.. }
|
967
|
+
end
|
968
|
+
```
|
969
|
+
|
970
|
+
If you want to capture variable of specific type, there is a type matcher for that case:
|
971
|
+
|
972
|
+
```ruby
|
973
|
+
matcher = Fear.matcher do |m|
|
974
|
+
m.xcase('[head : String, 2, *]') { |head: | head }
|
975
|
+
end
|
976
|
+
matcher.(['foo', 2]) #=> 'foo'
|
977
|
+
matcher.(['foo', 3]) #=> MatchError
|
978
|
+
matcher.([1, 2]) #=> MatchError
|
979
|
+
```
|
980
|
+
|
981
|
+
You can extract variables from more complex objects. Fear packed with extractors for monads and `Date` object:
|
735
982
|
|
736
983
|
```ruby
|
737
|
-
|
738
|
-
m.
|
984
|
+
Fear.matcher do |m|
|
985
|
+
m.xcase('Date(year, 2, 29)', ->(year:) { year < 2000 }) do |year:|
|
986
|
+
"#{year} is a leap year before Millennium"
|
987
|
+
end
|
988
|
+
|
989
|
+
m.xcase('Date(year, 2, 29)') do |year:|
|
990
|
+
"#{year} is a leap year after Millennium"
|
991
|
+
end
|
992
|
+
|
993
|
+
m.case(Date) do |date|
|
994
|
+
"#{date.year} is not a leap year"
|
995
|
+
end
|
996
|
+
end
|
997
|
+
```
|
998
|
+
|
999
|
+
This matcher extracts values from date object and match against them at the same time
|
1000
|
+
|
1001
|
+
```ruby
|
1002
|
+
matcher.(Date.new(1996,02,29)) #=> "1996 is a leap year before Millennium"
|
1003
|
+
matcher.(Date.new(2004,02,29)) #=> "1996 is a leap year after Millennium"
|
1004
|
+
matcher.(Date.new(2003,01,24)) #=> "2003 is not a leap year"
|
739
1005
|
```
|
740
1006
|
|
741
|
-
|
1007
|
+
Nothing tricky here. The extractor object takes an object and tries to give back the arguments. It's like
|
1008
|
+
constructor, but instead of construction an object, it deconstructs it.
|
1009
|
+
|
1010
|
+
An argument of an extractor may be also a pattern or even introduce a new variable.
|
742
1011
|
|
743
1012
|
```ruby
|
744
|
-
|
745
|
-
m.
|
1013
|
+
matcher = Fear.matcher do |m|
|
1014
|
+
m.xcase('Some([status : Integer, body : String])') do |status:, body:|
|
1015
|
+
"#{body.bytesize} bytes received with code #{status}"
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
matcher.(Fear.some([200, 'hello'])) #=> "5 bytes received with code 200"
|
1020
|
+
matcher.(Fear.some(['hello', 200])) #=> MatchError
|
746
1021
|
```
|
747
1022
|
|
748
|
-
|
1023
|
+
You can provide extractors for you own classes
|
749
1024
|
|
750
1025
|
```ruby
|
751
|
-
|
752
|
-
|
1026
|
+
Fear.register_extractor(User, Fear.case(User) { |user| [user.id, user.email] }.lift)
|
1027
|
+
# is the same as
|
1028
|
+
Fear.register_extractor(User, proc do |user|
|
1029
|
+
if user.is_a?(User)
|
1030
|
+
Fear.some([user.id, user.email])
|
1031
|
+
else
|
1032
|
+
Fear.none
|
1033
|
+
end
|
1034
|
+
end)
|
753
1035
|
```
|
754
1036
|
|
755
|
-
|
1037
|
+
Now extracting user's id and email is possible:
|
756
1038
|
|
757
|
-
```ruby
|
758
|
-
matcher = Fear.matcher do |m|
|
759
|
-
m.case(Integer) { |n| "#{n} is a number" }
|
760
|
-
m.case(String) { |n| "#{n} is a string" }
|
761
|
-
m.else { |n| "#{n} is a #{n.class}" }
|
762
|
-
end
|
763
1039
|
|
764
|
-
|
765
|
-
|
766
|
-
|
1040
|
+
```ruby
|
1041
|
+
Fear.match(user) do |m|
|
1042
|
+
m.xcase('User(id, email)') { |id:, email:| }
|
1043
|
+
end
|
1044
|
+
```
|
767
1045
|
|
768
|
-
|
769
|
-
|
1046
|
+
Note, registered extractor should return either array of arguments, or boolean.
|
1047
|
+
|
1048
|
+
#### Extracting struct
|
1049
|
+
|
1050
|
+
There is predefined `Struct` extractor:
|
770
1051
|
|
771
1052
|
```ruby
|
772
|
-
|
773
|
-
Fear.matcher do |m|
|
774
|
-
m.case(0) { 'zero' }
|
775
|
-
m.case(->(n) { n < 10 }) { 'smaller than ten' }
|
776
|
-
m.case(->(n) { n > 10 }) { 'bigger than ten' }
|
777
|
-
end
|
778
|
-
)
|
1053
|
+
Envelope = Struct.new(:id, :receiver, :sender, :message)
|
779
1054
|
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
m.else { 'unexpected' }
|
1055
|
+
Fear.matcher do |m|
|
1056
|
+
m.xcase('envelope @ Envelope(id, _, sender, _)') do |id:, sender:, envelope:|
|
1057
|
+
acknowledge(id, sender)
|
1058
|
+
process(acknowledge)
|
785
1059
|
end
|
786
|
-
|
1060
|
+
end
|
1061
|
+
```
|
787
1062
|
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
1063
|
+
#### How to debug pattern extractors?
|
1064
|
+
|
1065
|
+
You can build pattern manually and ask for failure reason:
|
1066
|
+
|
1067
|
+
```ruby
|
1068
|
+
Fear['Some([:err, 444])'].failure_reason(Fear.some([:err, 445]))
|
1069
|
+
# =>
|
1070
|
+
Expected `445` to match:
|
1071
|
+
Some([:err, 444])
|
1072
|
+
~~~~~~~~~~~~^
|
1073
|
+
```
|
1074
|
+
|
1075
|
+
by the way you can also match against such pattern
|
1076
|
+
|
1077
|
+
```ruby
|
1078
|
+
Fear['Some([:err, 444])'] === Fear.some([:err, 445]) #=> false
|
1079
|
+
Fear['Some([:err, 444])'] === Fear.some([:err, 445]) #=> true
|
792
1080
|
```
|
793
1081
|
|
794
1082
|
#### More examples
|
@@ -811,7 +1099,6 @@ fibonnaci = Fear.matcher do |m|
|
|
811
1099
|
m.case(0) { 0 }
|
812
1100
|
m.case(1) { 1 }
|
813
1101
|
m.case(->(n) { n > 1}) { |n| fibonnaci.(n - 1) + fibonnaci.(n - 2) }
|
814
|
-
m.else { raise 'should be positive' }
|
815
1102
|
end
|
816
1103
|
|
817
1104
|
fibonnaci.(10) #=> 55
|
@@ -827,7 +1114,7 @@ only on container itself, but on enclosed value as well.
|
|
827
1114
|
Pattern match against an `Option`
|
828
1115
|
|
829
1116
|
```ruby
|
830
|
-
|
1117
|
+
Fear.some(42).match do |m|
|
831
1118
|
m.some { |x| x * 2 }
|
832
1119
|
m.none { 'none' }
|
833
1120
|
end #=> 84
|
@@ -836,9 +1123,9 @@ end #=> 84
|
|
836
1123
|
pattern match on enclosed value
|
837
1124
|
|
838
1125
|
```ruby
|
839
|
-
|
840
|
-
m.some(:even
|
841
|
-
m.some(:odd
|
1126
|
+
Fear.some(41).match do |m|
|
1127
|
+
m.some(:even?.to_proc) { |x| x / 2 }
|
1128
|
+
m.some(:odd?.to_proc, ->(v) { v > 0 }) { |x| x * 2 }
|
842
1129
|
m.none { 'none' }
|
843
1130
|
end #=> 82
|
844
1131
|
```
|
@@ -846,8 +1133,8 @@ end #=> 82
|
|
846
1133
|
it raises `Fear::MatchError` error if nothing matched. To avoid exception, you can pass `#else` branch
|
847
1134
|
|
848
1135
|
```ruby
|
849
|
-
|
850
|
-
m.some(:odd
|
1136
|
+
Fear.some(42).match do |m|
|
1137
|
+
m.some(:odd?.to_proc) { |x| x * 2 }
|
851
1138
|
m.else { 'nothing' }
|
852
1139
|
end #=> nothing
|
853
1140
|
```
|
@@ -857,16 +1144,81 @@ Pattern matching works the similar way for `Either` and `Try` monads.
|
|
857
1144
|
In sake of performance, you may want to generate pattern matching function and reuse it multiple times:
|
858
1145
|
|
859
1146
|
```ruby
|
860
|
-
matcher = Option.matcher do |m|
|
1147
|
+
matcher = Fear::Option.matcher do |m|
|
861
1148
|
m.some(42) { 'Yep' }
|
862
1149
|
m.some { 'Nope' }
|
863
1150
|
m.none { 'Error' }
|
864
1151
|
end
|
865
1152
|
|
866
|
-
matcher.(
|
867
|
-
matcher.(
|
1153
|
+
matcher.(Fear.some(42)) #=> 'Yep'
|
1154
|
+
matcher.(Fear.some(40)) #=> 'Nope'
|
868
1155
|
```
|
869
1156
|
|
1157
|
+
#### Under the hood
|
1158
|
+
|
1159
|
+
Pattern matcher is a combination of partial functions wrapped into nice DSL. Every partial function
|
1160
|
+
defined on domain described with guard.
|
1161
|
+
|
1162
|
+
```ruby
|
1163
|
+
pf = Fear.case(Integer) { |x| x / 2 }
|
1164
|
+
pf.defined_at?(4) #=> true
|
1165
|
+
pf.defined_at?('Foo') #=> false
|
1166
|
+
pf.call('Foo') #=> raises Fear::MatchError
|
1167
|
+
pf.call_or_else('Foo') { 'not a number' } #=> 'not a number'
|
1168
|
+
pf.call_or_else(4) { 'not a number' } #=> 2
|
1169
|
+
pf.lift.call('Foo') #=> Fear::None
|
1170
|
+
pf.lift.call(4) #=> Fear.some(2)
|
1171
|
+
```
|
1172
|
+
|
1173
|
+
It uses `#===` method under the hood, so you can pass:
|
1174
|
+
|
1175
|
+
* Class to check kind of an object.
|
1176
|
+
* Lambda to evaluate it against an object.
|
1177
|
+
* Any literal, like `4`, `"Foobar"`, `:not_found` etc.
|
1178
|
+
* Qo matcher -- `m.case(Qo[name: 'John']) { .... }`
|
1179
|
+
|
1180
|
+
Partial functions may be combined with each other:
|
1181
|
+
|
1182
|
+
```ruby
|
1183
|
+
is_even = Fear.case(->(arg) { arg % 2 == 0}) { |arg| "#{arg} is even" }
|
1184
|
+
is_odd = Fear.case(->(arg) { arg % 2 == 1}) { |arg| "#{arg} is odd" }
|
1185
|
+
|
1186
|
+
(10..20).map(&is_even.or_else(is_odd))
|
1187
|
+
|
1188
|
+
to_integer = Fear.case(String, &:to_i)
|
1189
|
+
integer_two_times = Fear.case(Integer) { |x| x * 2 }
|
1190
|
+
|
1191
|
+
two_times = to_integer.and_then(integer_two_times).or_else(integer_two_times)
|
1192
|
+
two_times.(4) #=> 8
|
1193
|
+
two_times.('42') #=> 84
|
1194
|
+
```
|
1195
|
+
|
1196
|
+
Since matcher is just a syntactic sugar for partial functions, you can combine matchers with partial
|
1197
|
+
functions and each other.
|
1198
|
+
|
1199
|
+
```ruby
|
1200
|
+
handle_numbers = Fear.case(Integer, &:itself).and_then(
|
1201
|
+
Fear.matcher do |m|
|
1202
|
+
m.case(0) { 'zero' }
|
1203
|
+
m.case(->(n) { n < 10 }) { 'smaller than ten' }
|
1204
|
+
m.case(->(n) { n > 10 }) { 'bigger than ten' }
|
1205
|
+
end
|
1206
|
+
)
|
1207
|
+
|
1208
|
+
handle_strings = Fear.case(String, &:itself).and_then(
|
1209
|
+
Fear.matcher do |m|
|
1210
|
+
m.case('zero') { 0 }
|
1211
|
+
m.case('one') { 1 }
|
1212
|
+
m.else { 'unexpected' }
|
1213
|
+
end
|
1214
|
+
)
|
1215
|
+
|
1216
|
+
handle = handle_numbers.or_else(handle_strings)
|
1217
|
+
handle.(0) #=> 'zero'
|
1218
|
+
handle.(12) #=> 'bigger than ten'
|
1219
|
+
handle.('one') #=> 1
|
1220
|
+
```
|
1221
|
+
|
870
1222
|
## Testing
|
871
1223
|
|
872
1224
|
To simplify testing, you may use [fear-rspec](https://github.com/bolshakov/fear-rspec) gem. It
|
@@ -882,6 +1234,7 @@ provides a bunch of rspec matchers.
|
|
882
1234
|
|
883
1235
|
## Alternatives
|
884
1236
|
|
1237
|
+
* [algebrick](https://github.com/pitr-ch/algebrick)
|
885
1238
|
* [deterministic](https://github.com/pzol/deterministic)
|
886
1239
|
* [dry-monads](https://github.com/dry-rb/dry-monads)
|
887
1240
|
* [kleisli](https://github.com/txus/kleisli)
|