fear 1.0.0 → 2.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 +4 -4
- data/.github/dependabot.yml +27 -0
- data/.github/workflows/rubocop.yml +39 -0
- data/.github/workflows/spec.yml +42 -0
- data/.rubocop.yml +4 -60
- data/.simplecov +17 -0
- data/CHANGELOG.md +29 -1
- data/Gemfile +5 -5
- data/Gemfile.lock +86 -50
- data/README.md +240 -209
- data/Rakefile +72 -65
- data/examples/pattern_extracting.rb +10 -8
- data/examples/pattern_matching_binary_tree_set.rb +7 -2
- data/examples/pattern_matching_number_in_words.rb +48 -42
- data/fear.gemspec +33 -34
- data/lib/dry/types/fear/option.rb +125 -0
- data/lib/dry/types/fear.rb +8 -0
- data/lib/fear/await.rb +33 -0
- data/lib/fear/awaitable.rb +28 -0
- data/lib/fear/either.rb +15 -4
- data/lib/fear/either_api.rb +4 -0
- data/lib/fear/either_pattern_match.rb +9 -5
- data/lib/fear/empty_partial_function.rb +3 -1
- data/lib/fear/failure.rb +7 -7
- data/lib/fear/failure_pattern_match.rb +4 -0
- data/lib/fear/for.rb +4 -2
- data/lib/fear/for_api.rb +5 -1
- data/lib/fear/future.rb +157 -82
- data/lib/fear/future_api.rb +17 -4
- data/lib/fear/left.rb +3 -9
- data/lib/fear/left_pattern_match.rb +2 -0
- data/lib/fear/none.rb +28 -10
- data/lib/fear/none_pattern_match.rb +2 -0
- data/lib/fear/option.rb +30 -2
- data/lib/fear/option_api.rb +4 -0
- data/lib/fear/option_pattern_match.rb +8 -3
- data/lib/fear/partial_function/and_then.rb +4 -2
- data/lib/fear/partial_function/any.rb +2 -0
- data/lib/fear/partial_function/combined.rb +3 -1
- data/lib/fear/partial_function/empty.rb +6 -0
- data/lib/fear/partial_function/guard/and.rb +2 -0
- data/lib/fear/partial_function/guard/and3.rb +2 -0
- data/lib/fear/partial_function/guard/or.rb +2 -0
- data/lib/fear/partial_function/guard.rb +8 -6
- data/lib/fear/partial_function/lifted.rb +2 -0
- data/lib/fear/partial_function/or_else.rb +5 -1
- data/lib/fear/partial_function.rb +18 -9
- data/lib/fear/partial_function_class.rb +3 -1
- data/lib/fear/pattern_match.rb +3 -11
- data/lib/fear/pattern_matching_api.rb +6 -28
- data/lib/fear/promise.rb +7 -5
- data/lib/fear/right.rb +3 -9
- data/lib/fear/right_biased.rb +5 -3
- data/lib/fear/right_pattern_match.rb +4 -0
- data/lib/fear/some.rb +35 -8
- data/lib/fear/some_pattern_match.rb +2 -0
- data/lib/fear/struct.rb +237 -0
- data/lib/fear/success.rb +7 -8
- data/lib/fear/success_pattern_match.rb +4 -0
- data/lib/fear/try.rb +8 -2
- data/lib/fear/try_api.rb +4 -0
- data/lib/fear/try_pattern_match.rb +9 -5
- data/lib/fear/unit.rb +6 -2
- data/lib/fear/utils.rb +14 -2
- data/lib/fear/version.rb +4 -1
- data/lib/fear.rb +26 -44
- data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
- data/spec/dry/types/fear/option/core_spec.rb +77 -0
- data/spec/dry/types/fear/option/default_spec.rb +21 -0
- data/spec/dry/types/fear/option/hash_spec.rb +58 -0
- data/spec/dry/types/fear/option/option_spec.rb +97 -0
- data/spec/fear/awaitable_spec.rb +19 -0
- data/spec/fear/done_spec.rb +7 -5
- data/spec/fear/either/mixin_spec.rb +4 -2
- data/spec/fear/either_pattern_match_spec.rb +10 -8
- data/spec/fear/either_pattern_matching_spec.rb +28 -0
- data/spec/fear/either_spec.rb +26 -0
- data/spec/fear/failure_spec.rb +57 -70
- data/spec/fear/for/mixin_spec.rb +15 -0
- data/spec/fear/for_spec.rb +19 -17
- data/spec/fear/future_spec.rb +477 -237
- data/spec/fear/guard_spec.rb +136 -24
- data/spec/fear/left_spec.rb +57 -70
- data/spec/fear/none_spec.rb +39 -43
- data/spec/fear/option/mixin_spec.rb +9 -7
- data/spec/fear/option_pattern_match_spec.rb +10 -8
- data/spec/fear/option_pattern_matching_spec.rb +34 -0
- data/spec/fear/option_spec.rb +142 -0
- data/spec/fear/partial_function/any_spec.rb +25 -0
- data/spec/fear/partial_function/empty_spec.rb +12 -10
- data/spec/fear/partial_function_and_then_spec.rb +39 -37
- data/spec/fear/partial_function_composition_spec.rb +46 -44
- data/spec/fear/partial_function_or_else_spec.rb +92 -90
- data/spec/fear/partial_function_spec.rb +91 -61
- data/spec/fear/pattern_match_spec.rb +19 -51
- data/spec/fear/pattern_matching_api_spec.rb +31 -0
- data/spec/fear/promise_spec.rb +23 -23
- data/spec/fear/right_biased/left.rb +28 -26
- data/spec/fear/right_biased/right.rb +51 -49
- data/spec/fear/right_spec.rb +48 -68
- data/spec/fear/some_spec.rb +30 -40
- data/spec/fear/success_spec.rb +40 -60
- data/spec/fear/try/mixin_spec.rb +19 -3
- data/spec/fear/try_api_spec.rb +23 -0
- data/spec/fear/try_pattern_match_spec.rb +10 -8
- data/spec/fear/try_pattern_matching_spec.rb +34 -0
- data/spec/fear/utils_spec.rb +16 -14
- data/spec/spec_helper.rb +13 -7
- data/spec/struct_pattern_matching_spec.rb +36 -0
- data/spec/struct_spec.rb +194 -0
- data/spec/support/dry_types.rb +6 -0
- metadata +128 -87
- data/.travis.yml +0 -13
- data/lib/fear/extractor/anonymous_array_splat_matcher.rb +0 -8
- data/lib/fear/extractor/any_matcher.rb +0 -15
- data/lib/fear/extractor/array_head_matcher.rb +0 -34
- data/lib/fear/extractor/array_matcher.rb +0 -38
- data/lib/fear/extractor/array_splat_matcher.rb +0 -14
- data/lib/fear/extractor/empty_list_matcher.rb +0 -18
- data/lib/fear/extractor/extractor_matcher.rb +0 -42
- data/lib/fear/extractor/grammar.rb +0 -201
- data/lib/fear/extractor/grammar.treetop +0 -129
- data/lib/fear/extractor/identifier_matcher.rb +0 -16
- data/lib/fear/extractor/matcher/and.rb +0 -36
- data/lib/fear/extractor/matcher.rb +0 -54
- data/lib/fear/extractor/named_array_splat_matcher.rb +0 -15
- data/lib/fear/extractor/pattern.rb +0 -55
- data/lib/fear/extractor/typed_identifier_matcher.rb +0 -24
- data/lib/fear/extractor/value_matcher.rb +0 -17
- data/lib/fear/extractor.rb +0 -108
- data/lib/fear/extractor_api.rb +0 -33
- data/spec/fear/extractor/array_matcher_spec.rb +0 -228
- data/spec/fear/extractor/extractor_matcher_spec.rb +0 -151
- data/spec/fear/extractor/grammar_array_spec.rb +0 -23
- data/spec/fear/extractor/identified_matcher_spec.rb +0 -47
- data/spec/fear/extractor/identifier_matcher_spec.rb +0 -66
- data/spec/fear/extractor/pattern_spec.rb +0 -32
- data/spec/fear/extractor/typed_identifier_matcher_spec.rb +0 -62
- data/spec/fear/extractor/value_matcher_number_spec.rb +0 -77
- data/spec/fear/extractor/value_matcher_string_spec.rb +0 -86
- data/spec/fear/extractor/value_matcher_symbol_spec.rb +0 -69
- data/spec/fear/extractor_api_spec.rb +0 -113
- data/spec/fear/extractor_spec.rb +0 -59
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Fear
|
2
|
-
[](https://travis-ci.org/bolshakov/fear)
|
3
2
|
[](https://badge.fury.io/rb/fear)
|
3
|
+

|
4
|
+
[](https://codeclimate.com/github/bolshakov/fear/maintainability)
|
5
|
+
[](https://coveralls.io/github/bolshakov/fear?branch=master)
|
4
6
|
|
5
7
|
This gem provides `Option`, `Either`, and `Try` monads implemented an idiomatic way.
|
6
8
|
It is highly inspired by scala's implementation.
|
@@ -23,11 +25,11 @@ Or install it yourself as:
|
|
23
25
|
|
24
26
|
## Usage
|
25
27
|
|
26
|
-
* [Option](#option-documentation)
|
27
|
-
* [Try](#try-documentation)
|
28
|
-
* [Either](#either-documentation)
|
29
|
-
* [Future](#future-documentation)
|
30
|
-
* [For composition](#for-composition)
|
28
|
+
* [Option](#option-api-documentation)
|
29
|
+
* [Try](#try-api-documentation)
|
30
|
+
* [Either](#either-api-documentation)
|
31
|
+
* [Future](#future-api-documentation)
|
32
|
+
* [For composition](#for-composition-api-documentation)
|
31
33
|
* [Pattern Matching](#pattern-matching-api-documentation)
|
32
34
|
|
33
35
|
### Option ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Option))
|
@@ -38,7 +40,7 @@ Represents optional (nullable) values. Instances of `Option` are either an insta
|
|
38
40
|
The most idiomatic way to use an `Option` instance is to treat it as a collection
|
39
41
|
|
40
42
|
```ruby
|
41
|
-
name = Fear.option(params[:name])
|
43
|
+
name = Fear.option(params[:name])
|
42
44
|
upper = name.map(&:strip).select { |n| n.length != 0 }.map(&:upcase)
|
43
45
|
puts upper.get_or_else('')
|
44
46
|
```
|
@@ -49,9 +51,11 @@ having to check for the existence of a value.
|
|
49
51
|
A less-idiomatic way to use `Option` values is via pattern matching
|
50
52
|
|
51
53
|
```ruby
|
52
|
-
Fear.option(params[:name])
|
53
|
-
|
54
|
-
|
54
|
+
case Fear.option(params[:name])
|
55
|
+
in Fear::Some(name)
|
56
|
+
name.strip.upcase
|
57
|
+
in Fear::None
|
58
|
+
'No name value'
|
55
59
|
end
|
56
60
|
```
|
57
61
|
|
@@ -66,17 +70,15 @@ else
|
|
66
70
|
end
|
67
71
|
```
|
68
72
|
|
69
|
-
Alternatively,
|
73
|
+
Alternatively, you can use camel-case factory methods `Fear::Option()`, `Fear::Some()` and `Fear::None` methods:
|
70
74
|
|
71
75
|
```ruby
|
72
|
-
|
73
|
-
|
74
|
-
Option(42) #=> #<Fear::Some get=42>
|
75
|
-
Option(nil) #=> #<Fear::None>
|
76
|
+
Fear::Option(42) #=> #<Fear::Some get=42>
|
77
|
+
Fear::Option(nil) #=> #<Fear::None>
|
76
78
|
|
77
|
-
Some(42) #=> #<Fear::Some get=42>
|
78
|
-
Some(nil) #=> #<Fear::Some get=nil>
|
79
|
-
None
|
79
|
+
Fear::Some(42) #=> #<Fear::Some get=42>
|
80
|
+
Fear::Some(nil) #=> #<Fear::Some get=nil>
|
81
|
+
Fear::None #=> #<Fear::None>
|
80
82
|
```
|
81
83
|
|
82
84
|
#### Option#get_or_else
|
@@ -101,7 +103,7 @@ Fear.none.or_else { Fear.some(21) } #=> Fear.some(21)
|
|
101
103
|
Fear.none.or_else { None } #=> None
|
102
104
|
```
|
103
105
|
|
104
|
-
#### Option#
|
106
|
+
#### Option#include?
|
105
107
|
|
106
108
|
Checks if `Option` has an element that is equal (as determined by `==`) to given values.
|
107
109
|
|
@@ -143,9 +145,9 @@ Fear.none.flat_map { |v| Fear.some(v/2) } #=> None
|
|
143
145
|
Returns `false` if `None` or returns the result of the application of the given predicate to the `Some` value.
|
144
146
|
|
145
147
|
```ruby
|
146
|
-
Fear.some(12).any?
|
147
|
-
Fear.some(7).any?
|
148
|
-
Fear.none.any?
|
148
|
+
Fear.some(12).any? { |v| v > 10 } #=> true
|
149
|
+
Fear.some(7).any? { |v| v > 10 } #=> false
|
150
|
+
Fear.none.any? { |v| v > 10 } #=> false
|
149
151
|
```
|
150
152
|
|
151
153
|
#### Option#select
|
@@ -153,12 +155,24 @@ Fear.none.any?( |v| v > 10) #=> false
|
|
153
155
|
Returns self if it is nonempty and applying the predicate to this `Option`'s value returns `true`. Otherwise,
|
154
156
|
return `None`.
|
155
157
|
|
156
|
-
```ruby
|
157
|
-
Fear.some(42).select { |v| v > 40 } #=> Fear.
|
158
|
+
```ruby
|
159
|
+
Fear.some(42).select { |v| v > 40 } #=> Fear.some(42)
|
158
160
|
Fear.some(42).select { |v| v < 40 } #=> None
|
159
161
|
Fear.none.select { |v| v < 40 } #=> None
|
160
162
|
```
|
161
163
|
|
164
|
+
#### Option#filter_map
|
165
|
+
|
166
|
+
Returns a new `Some` of truthy results (everything except `false` or `nil`) of
|
167
|
+
running the block or `None` otherwise.
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
Fear.some(42).filter_map { |v| v/2 if v.even? } #=> Fear.some(21)
|
171
|
+
Fear.some(42).filter_map { |v| v/2 if v.odd? } #=> Fear.none
|
172
|
+
Fear.some(42).filter_map { |v| false } #=> Fear.none
|
173
|
+
Fear.none.filter_map { |v| v/2 } #=> Fear.none
|
174
|
+
```
|
175
|
+
|
162
176
|
#### Option#reject
|
163
177
|
|
164
178
|
Returns `Some` if applying the predicate to this `Option`'s value returns `false`. Otherwise, return `None`.
|
@@ -182,6 +196,37 @@ Fear.some(42).empty? #=> false
|
|
182
196
|
Fear.none.empty? #=> true
|
183
197
|
```
|
184
198
|
|
199
|
+
#### Option#blank?
|
200
|
+
|
201
|
+
Returns `true` if the `Option` is `None`, `false` otherwise.
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
Fear.some(42).blank? #=> false
|
205
|
+
Fear.none.blank? #=> true
|
206
|
+
```
|
207
|
+
|
208
|
+
#### Option#present?
|
209
|
+
|
210
|
+
Returns `false` if the `Option` is `None`, `true` otherwise.
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
Fear.some(42).present? #=> true
|
214
|
+
Fear.none.present? #=> false
|
215
|
+
```
|
216
|
+
|
217
|
+
#### Option#zip
|
218
|
+
|
219
|
+
Returns a `Fear::Some` formed from this Option and another Option by combining the corresponding elements in a pair.
|
220
|
+
If either of the two options is empty, `Fear::None` is returned.
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
Fear.some("foo").zip(Fear.some("bar")) #=> Fear.some(["foo", "bar"])
|
224
|
+
Fear.some("foo").zip(Fear.some("bar")) { |x, y| x + y } #=> Fear.some("foobar")
|
225
|
+
Fear.some("foo").zip(Fear.none) #=> Fear.none
|
226
|
+
Fear.none.zip(Fear.some("bar")) #=> Fear.none
|
227
|
+
|
228
|
+
```
|
229
|
+
|
185
230
|
@see https://github.com/scala/scala/blob/2.11.x/src/library/scala/Option.scala
|
186
231
|
|
187
232
|
|
@@ -201,19 +246,14 @@ dividend = Fear.try { Integer(params[:dividend]) }
|
|
201
246
|
divisor = Fear.try { Integer(params[:divisor]) }
|
202
247
|
problem = dividend.flat_map { |x| divisor.map { |y| x / y } }
|
203
248
|
|
204
|
-
problem
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
m.failure do |exception|
|
214
|
-
puts "You entered something wrong. Try again"
|
215
|
-
puts "Info from the exception: #{exception.message}"
|
216
|
-
end
|
249
|
+
case problem
|
250
|
+
in Fear::Success(result)
|
251
|
+
puts "Result of #{dividend.get} / #{divisor.get} is: #{result}"
|
252
|
+
in Fear::Failure(ZeroDivisionError)
|
253
|
+
puts "Division by zero is not allowed"
|
254
|
+
in Fear::Failure(exception)
|
255
|
+
puts "You entered something wrong. Try again"
|
256
|
+
puts "Info from the exception: #{exception.message}"
|
217
257
|
end
|
218
258
|
```
|
219
259
|
|
@@ -230,13 +270,11 @@ type of default behavior in the case of failure.
|
|
230
270
|
*NOTE*: Only non-fatal exceptions are caught by the combinators on `Try`.
|
231
271
|
Serious system errors, on the other hand, will be thrown.
|
232
272
|
|
233
|
-
Alternatively, include
|
273
|
+
Alternatively, include you can use camel-case factory method `Fear::Try()`:
|
234
274
|
|
235
275
|
```ruby
|
236
|
-
|
237
|
-
|
238
|
-
Try { 4/0 } #=> #<Fear::Failure exception=...>
|
239
|
-
Try { 4/2 } #=> #<Fear::Success value=2>
|
276
|
+
Fear::Try { 4/0 } #=> #<Fear::Failure exception=...>
|
277
|
+
Fear::Try { 4/2 } #=> #<Fear::Success value=2>
|
240
278
|
```
|
241
279
|
|
242
280
|
#### Try#get_or_else
|
@@ -291,7 +329,7 @@ Fear.failure(ArgumentError.new).flat_map { |v| Fear.success(v/2) } #=> Fear.fail
|
|
291
329
|
Returns an `Some` containing the `Success` value or a `None` if this is a `Failure`.
|
292
330
|
|
293
331
|
```ruby
|
294
|
-
Fear.success(42).to_option #=> Fear.some(
|
332
|
+
Fear.success(42).to_option #=> Fear.some(42)
|
295
333
|
Fear.failure(ArgumentError.new).to_option #=> None
|
296
334
|
```
|
297
335
|
|
@@ -300,9 +338,9 @@ Fear.failure(ArgumentError.new).to_option #=> None
|
|
300
338
|
Returns `false` if `Failure` or returns the result of the application of the given predicate to the `Success` value.
|
301
339
|
|
302
340
|
```ruby
|
303
|
-
Fear.success(12).any?
|
304
|
-
Fear.success(7).any?
|
305
|
-
Fear.failure(ArgumentError.new).any?
|
341
|
+
Fear.success(12).any? { |v| v > 10 } #=> true
|
342
|
+
Fear.success(7).any? { |v| v > 10 } #=> false
|
343
|
+
Fear.failure(ArgumentError.new).any? { |v| v > 10 } #=> false
|
306
344
|
```
|
307
345
|
|
308
346
|
#### Try#success? and Try#failure?
|
@@ -352,7 +390,7 @@ Converts this to a `Failure` if the predicate is not satisfied.
|
|
352
390
|
|
353
391
|
```ruby
|
354
392
|
Fear.success(42).select { |v| v > 40 }
|
355
|
-
#=> Fear.success(
|
393
|
+
#=> Fear.success(42)
|
356
394
|
Fear.success(42).select { |v| v < 40 }
|
357
395
|
#=> Fear.failure(Fear::NoSuchElementError.new("Predicate does not hold for 42"))
|
358
396
|
Fear.failure(ArgumentError.new).select { |v| v < 40 }
|
@@ -414,7 +452,7 @@ Fear.success(42).to_either #=> Fear.right(42)
|
|
414
452
|
Fear.failure(ArgumentError.new).to_either #=> Fear.left(ArgumentError.new)
|
415
453
|
```
|
416
454
|
|
417
|
-
### Either ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/
|
455
|
+
### Either ([API Documentation](https://www.rubydoc.info/github/bolshakov/fear/master/Fear/Either))
|
418
456
|
|
419
457
|
Represents a value of one of two possible types (a disjoint union.)
|
420
458
|
An instance of `Either` is either an instance of `Left` or `Right`.
|
@@ -436,14 +474,11 @@ rescue ArgumentError
|
|
436
474
|
Fear.left(in)
|
437
475
|
end
|
438
476
|
|
439
|
-
result
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
m.left do |x|
|
445
|
-
"You passed me the String: #{x}"
|
446
|
-
end
|
477
|
+
case result
|
478
|
+
in Fear::Right(x)
|
479
|
+
"You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}"
|
480
|
+
in Fear::Left(x)
|
481
|
+
"You passed me the String: #{x}"
|
447
482
|
end
|
448
483
|
```
|
449
484
|
|
@@ -451,13 +486,11 @@ Either is right-biased, which means that `Right` is assumed to be the default ca
|
|
451
486
|
operate on. If it is `Left`, operations like `#map`, `#flat_map`, ... return the `Left` value
|
452
487
|
unchanged.
|
453
488
|
|
454
|
-
Alternatively,
|
489
|
+
Alternatively, you can use camel-case factory methods `Fear::Left()`, and `Fear::Right()`:
|
455
490
|
|
456
491
|
```ruby
|
457
|
-
|
458
|
-
|
459
|
-
Left(42) #=> #<Fear::Left value=42>
|
460
|
-
Right(42) #=> #<Fear::Right value=42>
|
492
|
+
Fear::Left(42) #=> #<Fear::Left value=42>
|
493
|
+
Fear::Right(42) #=> #<Fear::Right value=42>
|
461
494
|
```
|
462
495
|
|
463
496
|
#### Either#get_or_else
|
@@ -524,7 +557,7 @@ Fear.left('undefined').flat_map { |v| Fear.right(v/2) } #=> Fear.left('undefined
|
|
524
557
|
Returns an `Some` containing the `Right` value or a `None` if this is a `Left`.
|
525
558
|
|
526
559
|
```ruby
|
527
|
-
Fear.right(42).to_option #=> Fear.some(
|
560
|
+
Fear.right(42).to_option #=> Fear.some(42)
|
528
561
|
Fear.left('undefined').to_option #=> Fear::None
|
529
562
|
```
|
530
563
|
|
@@ -533,9 +566,9 @@ Fear.left('undefined').to_option #=> Fear::None
|
|
533
566
|
Returns `false` if `Left` or returns the result of the application of the given predicate to the `Right` value.
|
534
567
|
|
535
568
|
```ruby
|
536
|
-
Fear.right(12).any?
|
537
|
-
Fear.right(7).any?
|
538
|
-
Fear.left('undefined').any?
|
569
|
+
Fear.right(12).any? { |v| v > 10 } #=> true
|
570
|
+
Fear.right(7).any? { |v| v > 10 } #=> false
|
571
|
+
Fear.left('undefined').any? { |v| v > 10 } #=> false
|
539
572
|
```
|
540
573
|
|
541
574
|
#### Either#right?, Either#success?
|
@@ -625,7 +658,7 @@ Fear.left("flower").join_right #=> Fear.left("flower")
|
|
625
658
|
Fear.left(Fear.right("flower")).join_right #=> Fear.left(Fear.right("flower"))
|
626
659
|
```
|
627
660
|
|
628
|
-
#### Either#
|
661
|
+
#### Either#join_left
|
629
662
|
|
630
663
|
Joins an `Either` through `Left`. This method requires that the left side of this `Either` is itself an
|
631
664
|
`Either` type. This method, and `join_right`, are analogous to `Option#flatten`
|
@@ -764,7 +797,7 @@ end #=> returns new future of Fear.success(0)
|
|
764
797
|
|
765
798
|
If the future resolved to success or recovery matcher did not matched, it returns the future `Fear::Failure`.
|
766
799
|
|
767
|
-
The second option is `Future#
|
800
|
+
The second option is `Future#fallback_to` method. It allows to fallback to result of another future in case of failure
|
768
801
|
|
769
802
|
```ruby
|
770
803
|
future = Fear.future { fail 'error' }
|
@@ -784,6 +817,20 @@ end.and_then do |m|
|
|
784
817
|
end
|
785
818
|
```
|
786
819
|
|
820
|
+
#### Testing future values
|
821
|
+
|
822
|
+
Sometimes it may be helpful to await for future completion. You can await either future,
|
823
|
+
or result. Don't forget to pass timeout in seconds:
|
824
|
+
|
825
|
+
|
826
|
+
```ruby
|
827
|
+
future = Fear.future { 42 }
|
828
|
+
|
829
|
+
Fear::Await.result(future, 3) #=> 42
|
830
|
+
|
831
|
+
Fear::Await.ready(future, 3) #=> Fear::Future.successful(42)
|
832
|
+
```
|
833
|
+
|
787
834
|
### For composition ([API Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/ForApi))
|
788
835
|
|
789
836
|
Provides syntactic sugar for composition of multiple monadic operations.
|
@@ -922,144 +969,6 @@ matcher.(42) #=> "42 is a number"
|
|
922
969
|
matcher.(10..20) #=> "10..20 is a Range"
|
923
970
|
```
|
924
971
|
|
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 }
|
934
|
-
end
|
935
|
-
```
|
936
|
-
|
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 `_`
|
945
|
-
|
946
|
-
```ruby
|
947
|
-
matcher = Fear.matcher do |m|
|
948
|
-
m.xcase('[1, _, 3]') { .. }
|
949
|
-
end
|
950
|
-
```
|
951
|
-
|
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:
|
982
|
-
|
983
|
-
```ruby
|
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"
|
1005
|
-
```
|
1006
|
-
|
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.
|
1011
|
-
|
1012
|
-
```ruby
|
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
|
1021
|
-
```
|
1022
|
-
|
1023
|
-
You can provide extractors for you own classes
|
1024
|
-
|
1025
|
-
```ruby
|
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)
|
1035
|
-
```
|
1036
|
-
|
1037
|
-
Now extracting user's id and email is possible:
|
1038
|
-
|
1039
|
-
|
1040
|
-
```ruby
|
1041
|
-
Fear.match(user) do |m|
|
1042
|
-
m.xcase('User(id, email)') { |id:, email:| }
|
1043
|
-
end
|
1044
|
-
```
|
1045
|
-
|
1046
|
-
Note, registered extractor should return either array of arguments, or boolean.
|
1047
|
-
|
1048
|
-
#### Extracting struct
|
1049
|
-
|
1050
|
-
There is predefined `Struct` extractor:
|
1051
|
-
|
1052
|
-
```ruby
|
1053
|
-
Envelope = Struct.new(:id, :receiver, :sender, :message)
|
1054
|
-
|
1055
|
-
Fear.matcher do |m|
|
1056
|
-
m.xcase('envelope @ Envelope(id, _, sender, _)') do |id:, sender:, envelope:|
|
1057
|
-
acknowledge(id, sender)
|
1058
|
-
process(acknowledge)
|
1059
|
-
end
|
1060
|
-
end
|
1061
|
-
```
|
1062
|
-
|
1063
972
|
#### How to debug pattern extractors?
|
1064
973
|
|
1065
974
|
You can build pattern manually and ask for failure reason:
|
@@ -1095,13 +1004,13 @@ factorial.(10) #=> 3628800
|
|
1095
1004
|
Fibonacci number
|
1096
1005
|
|
1097
1006
|
```ruby
|
1098
|
-
|
1007
|
+
fibonacci = Fear.matcher do |m|
|
1099
1008
|
m.case(0) { 0 }
|
1100
1009
|
m.case(1) { 1 }
|
1101
|
-
m.case(->(n) { n > 1}) { |n|
|
1010
|
+
m.case(->(n) { n > 1}) { |n| fibonacci.(n - 1) + fibonacci.(n - 2) }
|
1102
1011
|
end
|
1103
1012
|
|
1104
|
-
|
1013
|
+
fibonacci.(10) #=> 55
|
1105
1014
|
```
|
1106
1015
|
|
1107
1016
|
Binary tree set implemented using pattern matching https://gist.github.com/bolshakov/3c51bbf7be95066d55d6d1ac8c605a1d
|
@@ -1157,7 +1066,7 @@ matcher.(Fear.some(40)) #=> 'Nope'
|
|
1157
1066
|
#### Under the hood
|
1158
1067
|
|
1159
1068
|
Pattern matcher is a combination of partial functions wrapped into nice DSL. Every partial function
|
1160
|
-
defined on domain described with guard.
|
1069
|
+
defined on domain described with a guard.
|
1161
1070
|
|
1162
1071
|
```ruby
|
1163
1072
|
pf = Fear.case(Integer) { |x| x / 2 }
|
@@ -1219,6 +1128,128 @@ handle.(12) #=> 'bigger than ten'
|
|
1219
1128
|
handle.('one') #=> 1
|
1220
1129
|
```
|
1221
1130
|
|
1131
|
+
### Native pattern-matching
|
1132
|
+
|
1133
|
+
Starting from ruby 2.7 you can use native pattern matching capabilities:
|
1134
|
+
|
1135
|
+
```ruby
|
1136
|
+
case Fear.some(42)
|
1137
|
+
in Fear::Some(x)
|
1138
|
+
x * 2
|
1139
|
+
in Fear::None
|
1140
|
+
'none'
|
1141
|
+
end #=> 84
|
1142
|
+
|
1143
|
+
case Fear.some(41)
|
1144
|
+
in Fear::Some(x) if x.even?
|
1145
|
+
x / 2
|
1146
|
+
in Fear::Some(x) if x.odd? && x > 0
|
1147
|
+
x * 2
|
1148
|
+
in Fear::None
|
1149
|
+
'none'
|
1150
|
+
end #=> 82
|
1151
|
+
|
1152
|
+
case Fear.some(42)
|
1153
|
+
in Fear::Some(x) if x.odd?
|
1154
|
+
x * 2
|
1155
|
+
else
|
1156
|
+
'nothing'
|
1157
|
+
end #=> nothing
|
1158
|
+
```
|
1159
|
+
|
1160
|
+
It's possible to pattern match against Fear::Either and Fear::Try as well:
|
1161
|
+
|
1162
|
+
```ruby
|
1163
|
+
case either
|
1164
|
+
in Fear::Right(Integer | String => x)
|
1165
|
+
"integer or string: #{x}"
|
1166
|
+
in Fear::Left(String => error_code) if error_code = :not_found
|
1167
|
+
'not found'
|
1168
|
+
end
|
1169
|
+
```
|
1170
|
+
|
1171
|
+
```ruby
|
1172
|
+
case Fear.try { 10 / x }
|
1173
|
+
in Fear::Failure(ZeroDivisionError)
|
1174
|
+
# ..
|
1175
|
+
in Fear::Success(x)
|
1176
|
+
# ..
|
1177
|
+
end
|
1178
|
+
```
|
1179
|
+
|
1180
|
+
### Dry-Types integration
|
1181
|
+
|
1182
|
+
#### Option
|
1183
|
+
|
1184
|
+
NOTE: Requires the dry-tyes gem to be loaded.
|
1185
|
+
|
1186
|
+
Load the `:fear_option` extension in your application.
|
1187
|
+
|
1188
|
+
```ruby
|
1189
|
+
require 'dry-types'
|
1190
|
+
require 'dry/types/fear'
|
1191
|
+
|
1192
|
+
Dry::Types.load_extensions(:fear_option)
|
1193
|
+
|
1194
|
+
module Types
|
1195
|
+
include Dry.Types()
|
1196
|
+
end
|
1197
|
+
```
|
1198
|
+
|
1199
|
+
Append .option to a Type to return a `Fear::Option` object:
|
1200
|
+
|
1201
|
+
```ruby
|
1202
|
+
Types::Option::Strict::Integer[nil]
|
1203
|
+
#=> Fear.none
|
1204
|
+
Types::Option::Coercible::String[nil]
|
1205
|
+
#=> Fear.none
|
1206
|
+
Types::Option::Strict::Integer[123]
|
1207
|
+
#=> Fear.some(123)
|
1208
|
+
Types::Option::Strict::String[123]
|
1209
|
+
#=> Fear.some(123)
|
1210
|
+
Types::Option::Coercible::Float['12.3']
|
1211
|
+
#=> Fear.some(12.3)
|
1212
|
+
```
|
1213
|
+
|
1214
|
+
'Option' types can also accessed by calling '.option' on a regular type:
|
1215
|
+
|
1216
|
+
```ruby
|
1217
|
+
Types::Strict::Integer.option # equivalent to Types::Option::Strict::Integer
|
1218
|
+
```
|
1219
|
+
|
1220
|
+
|
1221
|
+
You can define your own optional types:
|
1222
|
+
|
1223
|
+
```ruby
|
1224
|
+
option_string = Types::Strict::String.option
|
1225
|
+
option_string[nil]
|
1226
|
+
# => Fear.none
|
1227
|
+
option_string[nil].map(&:upcase)
|
1228
|
+
# => Fear.none
|
1229
|
+
option_string['something']
|
1230
|
+
# => Fear.some('something')
|
1231
|
+
option_string['something'].map(&:upcase)
|
1232
|
+
# => Fear.some('SOMETHING')
|
1233
|
+
option_string['something'].map(&:upcase).get_or_else { 'NOTHING' }
|
1234
|
+
# => "SOMETHING"
|
1235
|
+
```
|
1236
|
+
|
1237
|
+
You can use it with dry-struct as well:
|
1238
|
+
|
1239
|
+
```ruby
|
1240
|
+
class User < Dry::Struct
|
1241
|
+
attribute :name, Types::Coercible::String
|
1242
|
+
attribute :age, Types::Coercible::Integer.option
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
user = User.new(name: 'Bob', age: nil)
|
1246
|
+
user.name #=> "Bob"
|
1247
|
+
user.age #=> Fear.none
|
1248
|
+
|
1249
|
+
user = User.new(name: 'Bob', age: 42)
|
1250
|
+
user.age #=> Fear.some(42)
|
1251
|
+
```
|
1252
|
+
|
1222
1253
|
## Testing
|
1223
1254
|
|
1224
1255
|
To simplify testing, you may use [fear-rspec](https://github.com/bolshakov/fear-rspec) gem. It
|