fear 0.11.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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