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.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +27 -0
  3. data/.github/workflows/rubocop.yml +39 -0
  4. data/.github/workflows/spec.yml +42 -0
  5. data/.rubocop.yml +4 -60
  6. data/.simplecov +17 -0
  7. data/CHANGELOG.md +29 -1
  8. data/Gemfile +5 -5
  9. data/Gemfile.lock +86 -50
  10. data/README.md +240 -209
  11. data/Rakefile +72 -65
  12. data/examples/pattern_extracting.rb +10 -8
  13. data/examples/pattern_matching_binary_tree_set.rb +7 -2
  14. data/examples/pattern_matching_number_in_words.rb +48 -42
  15. data/fear.gemspec +33 -34
  16. data/lib/dry/types/fear/option.rb +125 -0
  17. data/lib/dry/types/fear.rb +8 -0
  18. data/lib/fear/await.rb +33 -0
  19. data/lib/fear/awaitable.rb +28 -0
  20. data/lib/fear/either.rb +15 -4
  21. data/lib/fear/either_api.rb +4 -0
  22. data/lib/fear/either_pattern_match.rb +9 -5
  23. data/lib/fear/empty_partial_function.rb +3 -1
  24. data/lib/fear/failure.rb +7 -7
  25. data/lib/fear/failure_pattern_match.rb +4 -0
  26. data/lib/fear/for.rb +4 -2
  27. data/lib/fear/for_api.rb +5 -1
  28. data/lib/fear/future.rb +157 -82
  29. data/lib/fear/future_api.rb +17 -4
  30. data/lib/fear/left.rb +3 -9
  31. data/lib/fear/left_pattern_match.rb +2 -0
  32. data/lib/fear/none.rb +28 -10
  33. data/lib/fear/none_pattern_match.rb +2 -0
  34. data/lib/fear/option.rb +30 -2
  35. data/lib/fear/option_api.rb +4 -0
  36. data/lib/fear/option_pattern_match.rb +8 -3
  37. data/lib/fear/partial_function/and_then.rb +4 -2
  38. data/lib/fear/partial_function/any.rb +2 -0
  39. data/lib/fear/partial_function/combined.rb +3 -1
  40. data/lib/fear/partial_function/empty.rb +6 -0
  41. data/lib/fear/partial_function/guard/and.rb +2 -0
  42. data/lib/fear/partial_function/guard/and3.rb +2 -0
  43. data/lib/fear/partial_function/guard/or.rb +2 -0
  44. data/lib/fear/partial_function/guard.rb +8 -6
  45. data/lib/fear/partial_function/lifted.rb +2 -0
  46. data/lib/fear/partial_function/or_else.rb +5 -1
  47. data/lib/fear/partial_function.rb +18 -9
  48. data/lib/fear/partial_function_class.rb +3 -1
  49. data/lib/fear/pattern_match.rb +3 -11
  50. data/lib/fear/pattern_matching_api.rb +6 -28
  51. data/lib/fear/promise.rb +7 -5
  52. data/lib/fear/right.rb +3 -9
  53. data/lib/fear/right_biased.rb +5 -3
  54. data/lib/fear/right_pattern_match.rb +4 -0
  55. data/lib/fear/some.rb +35 -8
  56. data/lib/fear/some_pattern_match.rb +2 -0
  57. data/lib/fear/struct.rb +237 -0
  58. data/lib/fear/success.rb +7 -8
  59. data/lib/fear/success_pattern_match.rb +4 -0
  60. data/lib/fear/try.rb +8 -2
  61. data/lib/fear/try_api.rb +4 -0
  62. data/lib/fear/try_pattern_match.rb +9 -5
  63. data/lib/fear/unit.rb +6 -2
  64. data/lib/fear/utils.rb +14 -2
  65. data/lib/fear/version.rb +4 -1
  66. data/lib/fear.rb +26 -44
  67. data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
  68. data/spec/dry/types/fear/option/core_spec.rb +77 -0
  69. data/spec/dry/types/fear/option/default_spec.rb +21 -0
  70. data/spec/dry/types/fear/option/hash_spec.rb +58 -0
  71. data/spec/dry/types/fear/option/option_spec.rb +97 -0
  72. data/spec/fear/awaitable_spec.rb +19 -0
  73. data/spec/fear/done_spec.rb +7 -5
  74. data/spec/fear/either/mixin_spec.rb +4 -2
  75. data/spec/fear/either_pattern_match_spec.rb +10 -8
  76. data/spec/fear/either_pattern_matching_spec.rb +28 -0
  77. data/spec/fear/either_spec.rb +26 -0
  78. data/spec/fear/failure_spec.rb +57 -70
  79. data/spec/fear/for/mixin_spec.rb +15 -0
  80. data/spec/fear/for_spec.rb +19 -17
  81. data/spec/fear/future_spec.rb +477 -237
  82. data/spec/fear/guard_spec.rb +136 -24
  83. data/spec/fear/left_spec.rb +57 -70
  84. data/spec/fear/none_spec.rb +39 -43
  85. data/spec/fear/option/mixin_spec.rb +9 -7
  86. data/spec/fear/option_pattern_match_spec.rb +10 -8
  87. data/spec/fear/option_pattern_matching_spec.rb +34 -0
  88. data/spec/fear/option_spec.rb +142 -0
  89. data/spec/fear/partial_function/any_spec.rb +25 -0
  90. data/spec/fear/partial_function/empty_spec.rb +12 -10
  91. data/spec/fear/partial_function_and_then_spec.rb +39 -37
  92. data/spec/fear/partial_function_composition_spec.rb +46 -44
  93. data/spec/fear/partial_function_or_else_spec.rb +92 -90
  94. data/spec/fear/partial_function_spec.rb +91 -61
  95. data/spec/fear/pattern_match_spec.rb +19 -51
  96. data/spec/fear/pattern_matching_api_spec.rb +31 -0
  97. data/spec/fear/promise_spec.rb +23 -23
  98. data/spec/fear/right_biased/left.rb +28 -26
  99. data/spec/fear/right_biased/right.rb +51 -49
  100. data/spec/fear/right_spec.rb +48 -68
  101. data/spec/fear/some_spec.rb +30 -40
  102. data/spec/fear/success_spec.rb +40 -60
  103. data/spec/fear/try/mixin_spec.rb +19 -3
  104. data/spec/fear/try_api_spec.rb +23 -0
  105. data/spec/fear/try_pattern_match_spec.rb +10 -8
  106. data/spec/fear/try_pattern_matching_spec.rb +34 -0
  107. data/spec/fear/utils_spec.rb +16 -14
  108. data/spec/spec_helper.rb +13 -7
  109. data/spec/struct_pattern_matching_spec.rb +36 -0
  110. data/spec/struct_spec.rb +194 -0
  111. data/spec/support/dry_types.rb +6 -0
  112. metadata +128 -87
  113. data/.travis.yml +0 -13
  114. data/lib/fear/extractor/anonymous_array_splat_matcher.rb +0 -8
  115. data/lib/fear/extractor/any_matcher.rb +0 -15
  116. data/lib/fear/extractor/array_head_matcher.rb +0 -34
  117. data/lib/fear/extractor/array_matcher.rb +0 -38
  118. data/lib/fear/extractor/array_splat_matcher.rb +0 -14
  119. data/lib/fear/extractor/empty_list_matcher.rb +0 -18
  120. data/lib/fear/extractor/extractor_matcher.rb +0 -42
  121. data/lib/fear/extractor/grammar.rb +0 -201
  122. data/lib/fear/extractor/grammar.treetop +0 -129
  123. data/lib/fear/extractor/identifier_matcher.rb +0 -16
  124. data/lib/fear/extractor/matcher/and.rb +0 -36
  125. data/lib/fear/extractor/matcher.rb +0 -54
  126. data/lib/fear/extractor/named_array_splat_matcher.rb +0 -15
  127. data/lib/fear/extractor/pattern.rb +0 -55
  128. data/lib/fear/extractor/typed_identifier_matcher.rb +0 -24
  129. data/lib/fear/extractor/value_matcher.rb +0 -17
  130. data/lib/fear/extractor.rb +0 -108
  131. data/lib/fear/extractor_api.rb +0 -33
  132. data/spec/fear/extractor/array_matcher_spec.rb +0 -228
  133. data/spec/fear/extractor/extractor_matcher_spec.rb +0 -151
  134. data/spec/fear/extractor/grammar_array_spec.rb +0 -23
  135. data/spec/fear/extractor/identified_matcher_spec.rb +0 -47
  136. data/spec/fear/extractor/identifier_matcher_spec.rb +0 -66
  137. data/spec/fear/extractor/pattern_spec.rb +0 -32
  138. data/spec/fear/extractor/typed_identifier_matcher_spec.rb +0 -62
  139. data/spec/fear/extractor/value_matcher_number_spec.rb +0 -77
  140. data/spec/fear/extractor/value_matcher_string_spec.rb +0 -86
  141. data/spec/fear/extractor/value_matcher_symbol_spec.rb +0 -69
  142. data/spec/fear/extractor_api_spec.rb +0 -113
  143. data/spec/fear/extractor_spec.rb +0 -59
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Fear
2
- [![Build Status](https://travis-ci.org/bolshakov/fear.svg?branch=master)](https://travis-ci.org/bolshakov/fear)
3
2
  [![Gem Version](https://badge.fury.io/rb/fear.svg)](https://badge.fury.io/rb/fear)
3
+ ![Specs](https://github.com/bolshakov/fear/workflows/Spec/badge.svg)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/01030620c59e9f40961b/maintainability)](https://codeclimate.com/github/bolshakov/fear/maintainability)
5
+ [![Coverage Status](https://coveralls.io/repos/github/bolshakov/fear/badge.svg?branch=master)](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]).match do |m|
53
- m.some { |name| name.strip.upcase }
54
- m.none { 'No name value' }
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, include `Fear::Option::Mixin` to use `Option()`, `Some()` and `None()` methods:
73
+ Alternatively, you can use camel-case factory methods `Fear::Option()`, `Fear::Some()` and `Fear::None` methods:
70
74
 
71
75
  ```ruby
72
- include Fear::Option::Mixin
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() #=> #<Fear::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#inlude?
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?( |v| v > 10) #=> true
147
- Fear.some(7).any?( |v| v > 10) #=> false
148
- Fear.none.any?( |v| v > 10) #=> false
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.success(21)
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.match |m|
205
- m.success do |result|
206
- puts "Result of #{dividend.get} / #{divisor.get} is: #{result}"
207
- end
208
-
209
- m.failure(ZeroDivisionError) do
210
- puts "Division by zero is not allowed"
211
- end
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 `Fear::Try::Mixin` to use `Try()` method:
273
+ Alternatively, include you can use camel-case factory method `Fear::Try()`:
234
274
 
235
275
  ```ruby
236
- include Fear::Try::Mixin
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(21)
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?( |v| v > 10) #=> true
304
- Fear.success(7).any?( |v| v > 10) #=> false
305
- Fear.failure(ArgumentError.new).any?( |v| v > 10) #=> false
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(21)
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/Option))
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.match do |m|
440
- m.right do |x|
441
- "You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}"
442
- end
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, include `Fear::Either::Mixin` to use `Left()`, and `Right()` methods:
489
+ Alternatively, you can use camel-case factory methods `Fear::Left()`, and `Fear::Right()`:
455
490
 
456
491
  ```ruby
457
- include Fear::Either::Mixin
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(21)
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?( |v| v > 10) #=> true
537
- Fear.right(7).any?( |v| v > 10) #=> false
538
- Fear.left('undefined').any?( |v| v > 10) #=> false
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#join_right
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#fallbock_to` method. It allows to fallback to result of another future in case of failure
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
- fibonnaci = Fear.matcher do |m|
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| fibonnaci.(n - 1) + fibonnaci.(n - 2) }
1010
+ m.case(->(n) { n > 1}) { |n| fibonacci.(n - 1) + fibonacci.(n - 2) }
1102
1011
  end
1103
1012
 
1104
- fibonnaci.(10) #=> 55
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