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.
Files changed (110) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -1
  3. data/.rubocop.yml +18 -0
  4. data/.travis.yml +0 -3
  5. data/CHANGELOG.md +12 -1
  6. data/Gemfile +1 -0
  7. data/{gemfiles/dry_equalizer_0.2.1.gemfile.lock → Gemfile.lock} +21 -12
  8. data/README.md +594 -241
  9. data/Rakefile +166 -219
  10. data/benchmarks/README.md +1 -0
  11. data/benchmarks/dry_do_vs_fear_for.txt +11 -0
  12. data/benchmarks/dry_some_fmap_vs_fear_some_map.txt +11 -0
  13. data/benchmarks/factorial.txt +16 -0
  14. data/benchmarks/fear_gaurd_and1_vs_new.txt +13 -0
  15. data/benchmarks/fear_gaurd_and2_vs_and.txt +13 -0
  16. data/benchmarks/fear_gaurd_and3_vs_and_and.txt +13 -0
  17. data/benchmarks/fear_pattern_extracting_with_vs_without_cache.txt +11 -0
  18. data/benchmarks/fear_pattern_matching_construction_vs_execution.txt +13 -0
  19. data/benchmarks/pattern_matching_dry_vs_qo_vs_fear_try.txt +14 -0
  20. data/benchmarks/pattern_matching_qo_vs_fear_pattern_extraction.txt +11 -0
  21. data/benchmarks/pattern_matching_qo_vs_fear_try_execution.txt +11 -0
  22. data/examples/pattern_extracting.rb +15 -0
  23. data/examples/pattern_matching_binary_tree_set.rb +96 -0
  24. data/examples/pattern_matching_number_in_words.rb +54 -0
  25. data/fear.gemspec +4 -2
  26. data/lib/fear.rb +21 -4
  27. data/lib/fear/either.rb +77 -59
  28. data/lib/fear/either_api.rb +21 -0
  29. data/lib/fear/empty_partial_function.rb +1 -1
  30. data/lib/fear/extractor.rb +108 -0
  31. data/lib/fear/extractor/anonymous_array_splat_matcher.rb +8 -0
  32. data/lib/fear/extractor/any_matcher.rb +15 -0
  33. data/lib/fear/extractor/array_head_matcher.rb +34 -0
  34. data/lib/fear/extractor/array_matcher.rb +38 -0
  35. data/lib/fear/extractor/array_splat_matcher.rb +14 -0
  36. data/lib/fear/extractor/empty_list_matcher.rb +18 -0
  37. data/lib/fear/extractor/extractor_matcher.rb +42 -0
  38. data/lib/fear/extractor/grammar.rb +201 -0
  39. data/lib/fear/extractor/grammar.treetop +129 -0
  40. data/lib/fear/extractor/identifier_matcher.rb +16 -0
  41. data/lib/fear/extractor/matcher.rb +54 -0
  42. data/lib/fear/extractor/matcher/and.rb +36 -0
  43. data/lib/fear/extractor/named_array_splat_matcher.rb +15 -0
  44. data/lib/fear/extractor/pattern.rb +55 -0
  45. data/lib/fear/extractor/typed_identifier_matcher.rb +24 -0
  46. data/lib/fear/extractor/value_matcher.rb +17 -0
  47. data/lib/fear/extractor_api.rb +33 -0
  48. data/lib/fear/failure.rb +32 -10
  49. data/lib/fear/for.rb +14 -69
  50. data/lib/fear/for_api.rb +66 -0
  51. data/lib/fear/future.rb +414 -0
  52. data/lib/fear/future_api.rb +19 -0
  53. data/lib/fear/left.rb +8 -0
  54. data/lib/fear/none.rb +17 -8
  55. data/lib/fear/option.rb +55 -49
  56. data/lib/fear/option_api.rb +38 -0
  57. data/lib/fear/partial_function.rb +9 -12
  58. data/lib/fear/partial_function/empty.rb +1 -1
  59. data/lib/fear/partial_function/guard.rb +8 -20
  60. data/lib/fear/partial_function/lifted.rb +1 -0
  61. data/lib/fear/partial_function_class.rb +10 -0
  62. data/lib/fear/pattern_match.rb +10 -0
  63. data/lib/fear/pattern_matching_api.rb +35 -11
  64. data/lib/fear/promise.rb +87 -0
  65. data/lib/fear/right.rb +8 -0
  66. data/lib/fear/some.rb +22 -3
  67. data/lib/fear/success.rb +22 -1
  68. data/lib/fear/try.rb +82 -67
  69. data/lib/fear/try_api.rb +31 -0
  70. data/lib/fear/unit.rb +28 -0
  71. data/lib/fear/version.rb +1 -1
  72. data/spec/fear/done_spec.rb +3 -3
  73. data/spec/fear/either/mixin_spec.rb +15 -0
  74. data/spec/fear/either_pattern_match_spec.rb +10 -12
  75. data/spec/fear/extractor/array_matcher_spec.rb +228 -0
  76. data/spec/fear/extractor/extractor_matcher_spec.rb +151 -0
  77. data/spec/fear/extractor/grammar_array_spec.rb +23 -0
  78. data/spec/fear/extractor/identified_matcher_spec.rb +47 -0
  79. data/spec/fear/extractor/identifier_matcher_spec.rb +66 -0
  80. data/spec/fear/extractor/pattern_spec.rb +32 -0
  81. data/spec/fear/extractor/typed_identifier_matcher_spec.rb +62 -0
  82. data/spec/fear/extractor/value_matcher_number_spec.rb +77 -0
  83. data/spec/fear/extractor/value_matcher_string_spec.rb +86 -0
  84. data/spec/fear/extractor/value_matcher_symbol_spec.rb +69 -0
  85. data/spec/fear/extractor_api_spec.rb +113 -0
  86. data/spec/fear/extractor_spec.rb +59 -0
  87. data/spec/fear/failure_spec.rb +73 -13
  88. data/spec/fear/for_spec.rb +35 -35
  89. data/spec/fear/future_spec.rb +466 -0
  90. data/spec/fear/guard_spec.rb +4 -4
  91. data/spec/fear/left_spec.rb +40 -14
  92. data/spec/fear/none_spec.rb +28 -12
  93. data/spec/fear/option/mixin_spec.rb +37 -0
  94. data/spec/fear/option_pattern_match_spec.rb +7 -9
  95. data/spec/fear/partial_function_spec.rb +25 -3
  96. data/spec/fear/pattern_match_spec.rb +33 -1
  97. data/spec/fear/promise_spec.rb +94 -0
  98. data/spec/fear/right_spec.rb +37 -9
  99. data/spec/fear/some_spec.rb +32 -6
  100. data/spec/fear/success_spec.rb +32 -4
  101. data/spec/fear/try/mixin_spec.rb +17 -0
  102. data/spec/fear/try_pattern_match_spec.rb +8 -10
  103. data/spec/spec_helper.rb +1 -1
  104. metadata +115 -20
  105. data/Appraisals +0 -32
  106. data/gemfiles/dry_equalizer_0.1.0.gemfile +0 -8
  107. data/gemfiles/dry_equalizer_0.1.0.gemfile.lock +0 -82
  108. data/gemfiles/dry_equalizer_0.2.1.gemfile +0 -8
  109. data/lib/fear/done.rb +0 -22
  110. data/spec/fear/option_spec.rb +0 -15
@@ -1,61 +1,6 @@
1
1
  module Fear
2
- # This class provides syntactic sugar for composition of
3
- # multiple monadic operations. It supports two such
4
- # operations - +flat_map+ and +map+. Any class providing them
5
- # is supported by +For+.
6
- #
7
- # For(Some(2), Some(3)) do |a, b|
8
- # a * b
9
- # end #=> Some(6)
10
- #
11
- # If one of operands is None, the result is None
12
- #
13
- # For(Some(2), None()) { |a, b| a * b } #=> None()
14
- # For(None(), Some(2)) { |a, b| a * b } #=> None()
15
- #
16
- # Lets look at first example:
17
- #
18
- # For(Some(2), Some(3)) { |a, b| a * b }
19
- #
20
- # it is translated to:
21
- #
22
- # Some(2).flat_map do |a|
23
- # Some(3).map do |b|
24
- # a * b
25
- # end
26
- # end
27
- #
28
- # It works with arrays as well
29
- #
30
- # For([1, 2], [2, 3], [3, 4]) { |a, b, c| a * b * c }
31
- # #=> [6, 8, 9, 12, 12, 16, 18, 24]
32
- #
33
- # it is translated to:
34
- #
35
- # [1, 2].flat_map do |a|
36
- # [2, 3].flat_map do |b|
37
- # [3, 4].map do |c|
38
- # a * b * c
39
- # end
40
- # end
41
- # end
42
- #
43
- # If you pass lambda instead of monad, it would be evaluated
44
- # only on demand.
45
- #
46
- # For(proc { None() }, proc { raise 'kaboom' } ) do |a, b|
47
- # a * b
48
- # end #=> None()
49
- #
50
- # It does not fail since `b` is not evaluated.
51
- # You can refer to previously defined monads from within lambdas.
52
- #
53
- # maybe_user = find_user('Paul') #=> <#Option value=<#User ...>>
54
- #
55
- # For(maybe_user, ->(user) { user.birthday }) do |user, birthday|
56
- # "#{user.name} was born on #{birthday}"
57
- # end #=> Some('Paul was born on 1987-06-17')
58
- #
2
+ # @api private
3
+ # @see Fear.for
59
4
  module For
60
5
  module_function # rubocop: disable Style/AccessModifierDeclarations
61
6
 
@@ -95,29 +40,29 @@ module Fear
95
40
  # @example
96
41
  # include Fear::For::Mixin
97
42
  #
98
- # For(Some(2), Some(3)) { |a, b| a * b } #=> Some(6)
99
- # For(Some(2), None()) { |a, b| a * b } #=> None()
43
+ # For(Fear.some(2), Fear.some(3)) { |a, b| a * b } #=> Fear.some(6)
44
+ # For(Fear.some(2), Fear.none()) { |a, b| a * b } #=> Fear.none()
100
45
  #
101
- # For(proc { Some(2) }, proc { Some(3) }) do |a, b|
46
+ # For(proc { Fear.some(2) }, proc { Fear.some(3) }) do |a, b|
102
47
  # a * b
103
- # end #=> Some(6)
48
+ # end #=> Fear.some(6)
104
49
  #
105
- # For(proc { None() }, proc { raise }) do |a, b|
50
+ # For(proc { Fear.none() }, proc { raise }) do |a, b|
106
51
  # a * b
107
- # end #=> None()
52
+ # end #=> Fear.none()
108
53
  #
109
- # For(Right(2), Right(3)) { |a, b| a * b } #=> Right(6)
110
- # For(Right(2), Left(3)) { |a, b| a * b } #=> Left(3)
54
+ # For(Fear.right(2), Fear.right(3)) { |a, b| a * b } #=> Fear.right(6)
55
+ # For(Fear.right(2), Fear.left(3)) { |a, b| a * b } #=> Fear.left(3)
111
56
  #
112
- # For(Success(2), Success(3)) { |a| a * b } #=> Success(3)
113
- # For(Success(2), Failure(...)) { |a, b| a * b } #=> Failure(...)
57
+ # For(Fear.success(2), Fear.success(3)) { |a| a * b } #=> Fear.success(3)
58
+ # For(Fear.success(2), Fear.failure(...)) { |a, b| a * b } #=> Fear.failure(...)
114
59
  #
115
60
  module Mixin
116
- # @param monads [Hash{Symbol => {#map, #flat_map}}]
61
+ # @param monads [{#map, #flat_map}]
117
62
  # @return [{#map, #flat_map}]
118
63
  #
119
64
  def For(*monads, &block)
120
- For.call(monads, &block)
65
+ Fear.for(*monads, &block)
121
66
  end
122
67
  end
123
68
  end
@@ -0,0 +1,66 @@
1
+ module Fear
2
+ module ForApi
3
+ # Syntactic sugar for composition of multiple monadic operations. It supports two such
4
+ # operations - +flat_map+ and +map+. Any class providing them
5
+ # is supported by +Fear.or+.
6
+ #
7
+ # Fear.for(Fear.some(2), Fear.some(3)) do |a, b|
8
+ # a * b
9
+ # end #=> Fear.some(6)
10
+ #
11
+ # If one of operands is None, the result is None
12
+ #
13
+ # Fear.for(Fear.some(2), Fear.none()) { |a, b| a * b } #=> Fear.none()
14
+ # Fear.for(Fear.none(), Fear.some(2)) { |a, b| a * b } #=> Fear.none()
15
+ #
16
+ # Lets look at first example:
17
+ #
18
+ # Fear.for(Fear.some(2), Fear.some(3)) { |a, b| a * b }
19
+ #
20
+ # it is translated to:
21
+ #
22
+ # Fear.some(2).flat_map do |a|
23
+ # Fear.some(3).map do |b|
24
+ # a * b
25
+ # end
26
+ # end
27
+ #
28
+ # It works with arrays as well
29
+ #
30
+ # Fear.for([1, 2], [2, 3], [3, 4]) { |a, b, c| a * b * c }
31
+ # #=> [6, 8, 9, 12, 12, 16, 18, 24]
32
+ #
33
+ # it is translated to:
34
+ #
35
+ # [1, 2].flat_map do |a|
36
+ # [2, 3].flat_map do |b|
37
+ # [3, 4].map do |c|
38
+ # a * b * c
39
+ # end
40
+ # end
41
+ # end
42
+ #
43
+ # If you pass lambda instead of monad, it would be evaluated
44
+ # only on demand.
45
+ #
46
+ # Fear.for(proc { Fear.none() }, proc { raise 'kaboom' } ) do |a, b|
47
+ # a * b
48
+ # end #=> Fear.none()
49
+ #
50
+ # It does not fail since `b` is not evaluated.
51
+ # You can refer to previously defined monads from within lambdas.
52
+ #
53
+ # maybe_user = find_user('Paul') #=> <#Option value=<#User ...>>
54
+ #
55
+ # Fear.for(maybe_user, ->(user) { user.birthday }) do |user, birthday|
56
+ # "#{user.name} was born on #{birthday}"
57
+ # end #=> Fear.some('Paul was born on 1987-06-17')
58
+ #
59
+ # @param monads [{#map, #flat_map}]
60
+ # @return [{#map, #flat_map}]
61
+ #
62
+ def for(*monads, &block)
63
+ Fear::For.call(monads, &block)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,414 @@
1
+ begin
2
+ require 'concurrent'
3
+ rescue LoadError
4
+ puts "You must add 'concurrent-ruby' to your Gemfile in order to use Fear::Future"
5
+ end
6
+ require_relative 'promise'
7
+
8
+ module Fear
9
+ # Asynchronous computations that yield futures are created
10
+ # with the +Fear.future+ call:
11
+ #
12
+ # @example
13
+ # success = "Hello"
14
+ # f = Fear.future { success + ' future!' }
15
+ # f.on_success do |result|
16
+ # puts result
17
+ # end
18
+ #
19
+ # Multiple callbacks may be registered; there is no guarantee
20
+ # that they will be executed in a particular order.
21
+ #
22
+ # The future may contain an exception and this means
23
+ # that the future failed. Futures obtained through combinators
24
+ # have the same error as the future they were obtained from.
25
+ #
26
+ # @example
27
+ # f = Fear.future { 5 }
28
+ # g = Fear.future { 3 }
29
+ # f.flat_map do |x|
30
+ # g.map { |y| x + y }
31
+ # end
32
+ #
33
+ # @example the same program may be written using +Fear.for+
34
+ # Fear.for(Fear.future { 5 }, Fear.future { 3 }) do |x, y|
35
+ # x + y
36
+ # end
37
+ #
38
+ # @see https://github.com/scala/scala/blob/2.11.x/src/library/scala/concurrent/Future.scala
39
+ #
40
+ class Future
41
+ # @param promise [nil, Concurrent::Future] converts
42
+ # +Concurrent::Future+ into +Fear::Future+.
43
+ # @param options [see Concurrent::Future] options will be passed
44
+ # directly to +Concurrent::Future+
45
+ # @yield given block and evaluate it in the future.
46
+ # @api private
47
+ def initialize(promise = nil, **options, &block)
48
+ if block_given? && promise
49
+ raise ArgumentError, 'pass block or future'
50
+ end
51
+
52
+ @options = options
53
+ @promise = promise || Concurrent::Promise.execute(@options) do
54
+ Fear.try(&block)
55
+ end
56
+ end
57
+ attr_reader :promise
58
+ private :promise
59
+
60
+ # Calls the provided callback When this future is completed successfully.
61
+ #
62
+ # If the future has already been completed with a value,
63
+ # this will either be applied immediately or be scheduled asynchronously.
64
+ # @yieldparam [value]
65
+ # @return [self]
66
+ # @see #transform
67
+ #
68
+ # @example
69
+ # Fear.future { }.on_success do |value|
70
+ # # ...
71
+ # end
72
+ #
73
+ def on_success(&block)
74
+ on_complete do |result|
75
+ result.each(&block)
76
+ end
77
+ end
78
+
79
+ # When this future is completed with a failure apply the provided callback to the error.
80
+ #
81
+ # If the future has already been completed with a failure,
82
+ # this will either be applied immediately or be scheduled asynchronously.
83
+ #
84
+ # Will not be called in case that the future is completed with a value.
85
+ # @yieldparam [StandardError]
86
+ # @return [self]
87
+ #
88
+ # @example
89
+ # Fear.future { }.on_failure do |error|
90
+ # if error.is_a?(HTTPError)
91
+ # # ...
92
+ # end
93
+ # end
94
+ #
95
+ def on_failure
96
+ on_complete do |result|
97
+ if result.failure?
98
+ yield result.exception
99
+ end
100
+ end
101
+ end
102
+
103
+ # When this future is completed call the provided block.
104
+ #
105
+ # If the future has already been completed,
106
+ # this will either be applied immediately or be scheduled asynchronously.
107
+ # @yieldparam [Fear::Try]
108
+ # @return [self]
109
+ #
110
+ # @example
111
+ # Fear.future { }.on_complete do |try|
112
+ # try.map(&:do_the_job)
113
+ # end
114
+ #
115
+ def on_complete
116
+ promise.add_observer do |_time, try, _error|
117
+ yield try
118
+ end
119
+ self
120
+ end
121
+
122
+ # Returns whether the future has already been completed with
123
+ # a value or an error.
124
+ #
125
+ # @return [true, false] +true+ if the future is already
126
+ # completed, +false+ otherwise.
127
+ #
128
+ # @example
129
+ # future = Fear.future { }
130
+ # future.completed? #=> false
131
+ # sleep(1)
132
+ # future.completed? #=> true
133
+ #
134
+ def completed?
135
+ promise.fulfilled?
136
+ end
137
+
138
+ # The value of this +Future+.
139
+ #
140
+ # @return [Fear::Option<Fear::Try>] if the future is not completed
141
+ # the returned value will be +Fear::None+. If the future is
142
+ # completed the value will be +Fear::Some<Fear::Success>+ if it
143
+ # contains a valid result, or +Fear::Some<Fear::Failure>+ if it
144
+ # contains an error.
145
+ #
146
+ def value
147
+ Fear.option(promise.value(0))
148
+ end
149
+
150
+ # Asynchronously processes the value in the future once the value
151
+ # becomes available.
152
+ #
153
+ # Will not be called if the future fails.
154
+ # @yieldparam [any] yields with successful feature value
155
+ # @see {#on_complete}
156
+ #
157
+ alias each on_success
158
+
159
+ # Creates a new future by applying the +success+ function to the successful
160
+ # result of this future, or the +failure+ function to the failed result.
161
+ # If there is any non-fatal error raised when +success+ or +failure+ is
162
+ # applied, that error will be propagated to the resulting future.
163
+ #
164
+ # @yieldparam success [#get] function that transforms a successful result of the
165
+ # receiver into a successful result of the returned future
166
+ # @yieldparam failure [#exception] function that transforms a failure of the
167
+ # receiver into a failure of the returned future
168
+ # @return [Fear::Future] a future that will be completed with the
169
+ # transformed value
170
+ #
171
+ # @example
172
+ # Fear.future { open('http://example.com').read }
173
+ # .transform(
174
+ # ->(value) { ... },
175
+ # ->(error) { ... },
176
+ # )
177
+ #
178
+ def transform(success, failure)
179
+ promise = Promise.new(@options)
180
+ on_complete do |try|
181
+ try.match do |m|
182
+ m.success { |value| promise.success(success.call(value)) }
183
+ m.failure { |error| promise.failure(failure.call(error)) }
184
+ end
185
+ end
186
+ promise.to_future
187
+ end
188
+
189
+ # Creates a new future by applying a block to the successful result of
190
+ # this future. If this future is completed with an error then the new
191
+ # future will also contain this error.
192
+ #
193
+ # @return [Fear::Future]
194
+ #
195
+ # @example
196
+ # future = Fear.future { 2 }
197
+ # future.map { |v| v * 2 } #=> the same as Fear.future { 2 * 2 }
198
+ #
199
+ def map(&block)
200
+ promise = Promise.new(@options)
201
+ on_complete do |try|
202
+ promise.complete!(try.map(&block))
203
+ end
204
+
205
+ promise.to_future
206
+ end
207
+
208
+ # Creates a new future by applying a block to the successful result of
209
+ # this future, and returns the result of the function as the new future.
210
+ # If this future is completed with an exception then the new future will
211
+ # also contain this exception.
212
+ #
213
+ # @yieldparam [any]
214
+ # @return [Fear::Future]
215
+ #
216
+ # @example
217
+ # f1 = Fear.future { 5 }
218
+ # f2 = Fear.future { 3 }
219
+ # f1.flat_map do |v1|
220
+ # f1.map do |v2|
221
+ # v2 * v1
222
+ # end
223
+ # end
224
+ #
225
+ def flat_map # rubocop: disable Metrics/MethodLength
226
+ promise = Promise.new(@options)
227
+ on_complete do |result|
228
+ result.match do |m|
229
+ m.failure { promise.complete!(result) }
230
+ m.success do |value|
231
+ begin
232
+ yield(value).on_complete { |callback_result| promise.complete!(callback_result) }
233
+ rescue StandardError => error
234
+ promise.failure!(error)
235
+ end
236
+ end
237
+ end
238
+ end
239
+ promise.to_future
240
+ end
241
+
242
+ # Creates a new future by filtering the value of the current future
243
+ # with a predicate.
244
+ #
245
+ # If the current future contains a value which satisfies the predicate,
246
+ # the new future will also hold that value. Otherwise, the resulting
247
+ # future will fail with a +NoSuchElementError+.
248
+ #
249
+ # If the current future fails, then the resulting future also fails.
250
+ #
251
+ # @yieldparam [#get]
252
+ # @return [Fear::Future]
253
+ #
254
+ # @example
255
+ # f = Fear.future { 5 }
256
+ # f.select { |value| value % 2 == 1 } # evaluates to 5
257
+ # f.select { |value| value % 2 == 0 } # fail with NoSuchElementError
258
+ #
259
+ def select
260
+ map do |result|
261
+ if yield(result)
262
+ result
263
+ else
264
+ raise NoSuchElementError, '#select predicate is not satisfied'
265
+ end
266
+ end
267
+ end
268
+
269
+ # Creates a new future that will handle any matching error that this
270
+ # future might contain. If there is no match, or if this future contains
271
+ # a valid result then the new future will contain the same.
272
+ #
273
+ # @return [Fear::Future]
274
+ #
275
+ # @example
276
+ # Fear.future { 6 / 0 }.recover { |error| 0 } # result: 0
277
+ # Fear.future { 6 / 0 }.recover do |m|
278
+ # m.case(ZeroDivisionError) { 0 }
279
+ # m.case(OtherTypeOfError) { |error| ... }
280
+ # end # result: 0
281
+ #
282
+ #
283
+ def recover(&block)
284
+ promise = Promise.new(@options)
285
+ on_complete do |try|
286
+ promise.complete!(try.recover(&block))
287
+ end
288
+
289
+ promise.to_future
290
+ end
291
+
292
+ # Zips the values of +self+ and +other+ future, and creates
293
+ # a new future holding the array of their results.
294
+ #
295
+ # If +self+ future fails, the resulting future is failed
296
+ # with the error stored in +self+.
297
+ # Otherwise, if +other+ future fails, the resulting future is failed
298
+ # with the error stored in +other+.
299
+ #
300
+ # @param other [Fear::Future]
301
+ # @return [Fear::Future]
302
+ #
303
+ # @example
304
+ # future1 = Fear.future { call_service1 }
305
+ # future1 = Fear.future { call_service2 }
306
+ # future1.zip(future2) #=> returns the same result as Fear.future { [call_service1, call_service2] },
307
+ # # but it performs two calls asynchronously
308
+ #
309
+ def zip(other) # rubocop: disable Metrics/MethodLength
310
+ promise = Promise.new(@options)
311
+ on_complete do |try_of_self|
312
+ try_of_self.match do |m|
313
+ m.success do |value|
314
+ other.on_complete do |try_of_other|
315
+ promise.complete!(try_of_other.map { |other_value| [value, other_value] })
316
+ end
317
+ end
318
+ m.failure do |error|
319
+ promise.failure!(error)
320
+ end
321
+ end
322
+ end
323
+
324
+ promise.to_future
325
+ end
326
+
327
+ # Creates a new future which holds the result of +self+ future if it
328
+ # was completed successfully, or, if not, the result of the +fallback+
329
+ # future if +fallback+ is completed successfully.
330
+ # If both futures are failed, the resulting future holds the error
331
+ # object of the first future.
332
+ #
333
+ # @param fallback [Fear::Future]
334
+ # @return [Fear::Future]
335
+ #
336
+ # @example
337
+ # f = Fear.future { fail 'error' }
338
+ # g = Fear.future { 5 }
339
+ # f.fallback_to(g) # evaluates to 5
340
+ #
341
+ # rubocop: disable Metrics/MethodLength
342
+ def fallback_to(fallback)
343
+ promise = Promise.new(@options)
344
+ on_complete do |try|
345
+ try.match do |m|
346
+ m.success { |value| promise.complete!(value) }
347
+ m.failure do |error|
348
+ fallback.on_complete do |fallback_try|
349
+ fallback_try.match do |m2|
350
+ m2.success { |value| promise.complete!(value) }
351
+ m2.failure { promise.failure!(error) }
352
+ end
353
+ end
354
+ end
355
+ end
356
+ end
357
+
358
+ promise.to_future
359
+ end
360
+ # rubocop: enable Metrics/MethodLength
361
+
362
+ # Applies the side-effecting block to the result of +self+ future,
363
+ # and returns a new future with the result of this future.
364
+ #
365
+ # This method allows one to enforce that the callbacks are executed in a
366
+ # specified order.
367
+ #
368
+ # @note that if one of the chained +and_then+ callbacks throws
369
+ # an error, that error is not propagated to the subsequent
370
+ # +and_then+ callbacks. Instead, the subsequent +and_then+ callbacks
371
+ # are given the original value of this future.
372
+ #
373
+ # @example The following example prints out +5+:
374
+ # f = Fear.future { 5 }
375
+ # f.and_then do
376
+ # fail 'runtime error'
377
+ # end.and_then do |m|
378
+ # m.success { |value| puts value } # it evaluates this branch
379
+ # m.failure { |error| puts error.massage }
380
+ # end
381
+ #
382
+ def and_then(&block)
383
+ promise = Promise.new(@options)
384
+ on_complete do |try|
385
+ Fear.try { try.match(&block) }
386
+ promise.complete!(try)
387
+ end
388
+
389
+ promise.to_future
390
+ end
391
+
392
+ class << self
393
+ # Creates an already completed +Future+ with the specified error.
394
+ # @param exception [StandardError]
395
+ # @return [Fear::Future]
396
+ #
397
+ def failed(exception)
398
+ new(executor: Concurrent::ImmediateExecutor.new) do
399
+ raise exception
400
+ end
401
+ end
402
+
403
+ # Creates an already completed +Future+ with the specified result.
404
+ # @param result [Object]
405
+ # @return [Fear::Future]
406
+ #
407
+ def successful(result)
408
+ new(executor: Concurrent::ImmediateExecutor.new) do
409
+ result
410
+ end
411
+ end
412
+ end
413
+ end
414
+ end