fear 0.11.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +0 -1
- data/.rubocop.yml +18 -0
- data/.travis.yml +0 -3
- data/CHANGELOG.md +12 -1
- data/Gemfile +1 -0
- data/{gemfiles/dry_equalizer_0.2.1.gemfile.lock → Gemfile.lock} +21 -12
- data/README.md +594 -241
- data/Rakefile +166 -219
- data/benchmarks/README.md +1 -0
- data/benchmarks/dry_do_vs_fear_for.txt +11 -0
- data/benchmarks/dry_some_fmap_vs_fear_some_map.txt +11 -0
- data/benchmarks/factorial.txt +16 -0
- data/benchmarks/fear_gaurd_and1_vs_new.txt +13 -0
- data/benchmarks/fear_gaurd_and2_vs_and.txt +13 -0
- data/benchmarks/fear_gaurd_and3_vs_and_and.txt +13 -0
- data/benchmarks/fear_pattern_extracting_with_vs_without_cache.txt +11 -0
- data/benchmarks/fear_pattern_matching_construction_vs_execution.txt +13 -0
- data/benchmarks/pattern_matching_dry_vs_qo_vs_fear_try.txt +14 -0
- data/benchmarks/pattern_matching_qo_vs_fear_pattern_extraction.txt +11 -0
- data/benchmarks/pattern_matching_qo_vs_fear_try_execution.txt +11 -0
- data/examples/pattern_extracting.rb +15 -0
- data/examples/pattern_matching_binary_tree_set.rb +96 -0
- data/examples/pattern_matching_number_in_words.rb +54 -0
- data/fear.gemspec +4 -2
- data/lib/fear.rb +21 -4
- data/lib/fear/either.rb +77 -59
- data/lib/fear/either_api.rb +21 -0
- data/lib/fear/empty_partial_function.rb +1 -1
- data/lib/fear/extractor.rb +108 -0
- data/lib/fear/extractor/anonymous_array_splat_matcher.rb +8 -0
- data/lib/fear/extractor/any_matcher.rb +15 -0
- data/lib/fear/extractor/array_head_matcher.rb +34 -0
- data/lib/fear/extractor/array_matcher.rb +38 -0
- data/lib/fear/extractor/array_splat_matcher.rb +14 -0
- data/lib/fear/extractor/empty_list_matcher.rb +18 -0
- data/lib/fear/extractor/extractor_matcher.rb +42 -0
- data/lib/fear/extractor/grammar.rb +201 -0
- data/lib/fear/extractor/grammar.treetop +129 -0
- data/lib/fear/extractor/identifier_matcher.rb +16 -0
- data/lib/fear/extractor/matcher.rb +54 -0
- data/lib/fear/extractor/matcher/and.rb +36 -0
- data/lib/fear/extractor/named_array_splat_matcher.rb +15 -0
- data/lib/fear/extractor/pattern.rb +55 -0
- data/lib/fear/extractor/typed_identifier_matcher.rb +24 -0
- data/lib/fear/extractor/value_matcher.rb +17 -0
- data/lib/fear/extractor_api.rb +33 -0
- data/lib/fear/failure.rb +32 -10
- data/lib/fear/for.rb +14 -69
- data/lib/fear/for_api.rb +66 -0
- data/lib/fear/future.rb +414 -0
- data/lib/fear/future_api.rb +19 -0
- data/lib/fear/left.rb +8 -0
- data/lib/fear/none.rb +17 -8
- data/lib/fear/option.rb +55 -49
- data/lib/fear/option_api.rb +38 -0
- data/lib/fear/partial_function.rb +9 -12
- data/lib/fear/partial_function/empty.rb +1 -1
- data/lib/fear/partial_function/guard.rb +8 -20
- data/lib/fear/partial_function/lifted.rb +1 -0
- data/lib/fear/partial_function_class.rb +10 -0
- data/lib/fear/pattern_match.rb +10 -0
- data/lib/fear/pattern_matching_api.rb +35 -11
- data/lib/fear/promise.rb +87 -0
- data/lib/fear/right.rb +8 -0
- data/lib/fear/some.rb +22 -3
- data/lib/fear/success.rb +22 -1
- data/lib/fear/try.rb +82 -67
- data/lib/fear/try_api.rb +31 -0
- data/lib/fear/unit.rb +28 -0
- data/lib/fear/version.rb +1 -1
- data/spec/fear/done_spec.rb +3 -3
- data/spec/fear/either/mixin_spec.rb +15 -0
- data/spec/fear/either_pattern_match_spec.rb +10 -12
- data/spec/fear/extractor/array_matcher_spec.rb +228 -0
- data/spec/fear/extractor/extractor_matcher_spec.rb +151 -0
- data/spec/fear/extractor/grammar_array_spec.rb +23 -0
- data/spec/fear/extractor/identified_matcher_spec.rb +47 -0
- data/spec/fear/extractor/identifier_matcher_spec.rb +66 -0
- data/spec/fear/extractor/pattern_spec.rb +32 -0
- data/spec/fear/extractor/typed_identifier_matcher_spec.rb +62 -0
- data/spec/fear/extractor/value_matcher_number_spec.rb +77 -0
- data/spec/fear/extractor/value_matcher_string_spec.rb +86 -0
- data/spec/fear/extractor/value_matcher_symbol_spec.rb +69 -0
- data/spec/fear/extractor_api_spec.rb +113 -0
- data/spec/fear/extractor_spec.rb +59 -0
- data/spec/fear/failure_spec.rb +73 -13
- data/spec/fear/for_spec.rb +35 -35
- data/spec/fear/future_spec.rb +466 -0
- data/spec/fear/guard_spec.rb +4 -4
- data/spec/fear/left_spec.rb +40 -14
- data/spec/fear/none_spec.rb +28 -12
- data/spec/fear/option/mixin_spec.rb +37 -0
- data/spec/fear/option_pattern_match_spec.rb +7 -9
- data/spec/fear/partial_function_spec.rb +25 -3
- data/spec/fear/pattern_match_spec.rb +33 -1
- data/spec/fear/promise_spec.rb +94 -0
- data/spec/fear/right_spec.rb +37 -9
- data/spec/fear/some_spec.rb +32 -6
- data/spec/fear/success_spec.rb +32 -4
- data/spec/fear/try/mixin_spec.rb +17 -0
- data/spec/fear/try_pattern_match_spec.rb +8 -10
- data/spec/spec_helper.rb +1 -1
- metadata +115 -20
- data/Appraisals +0 -32
- data/gemfiles/dry_equalizer_0.1.0.gemfile +0 -8
- data/gemfiles/dry_equalizer_0.1.0.gemfile.lock +0 -82
- data/gemfiles/dry_equalizer_0.2.1.gemfile +0 -8
- data/lib/fear/done.rb +0 -22
- data/spec/fear/option_spec.rb +0 -15
data/lib/fear/for.rb
CHANGED
@@ -1,61 +1,6 @@
|
|
1
1
|
module Fear
|
2
|
-
#
|
3
|
-
#
|
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(
|
99
|
-
# For(
|
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 {
|
46
|
+
# For(proc { Fear.some(2) }, proc { Fear.some(3) }) do |a, b|
|
102
47
|
# a * b
|
103
|
-
# end #=>
|
48
|
+
# end #=> Fear.some(6)
|
104
49
|
#
|
105
|
-
# For(proc {
|
50
|
+
# For(proc { Fear.none() }, proc { raise }) do |a, b|
|
106
51
|
# a * b
|
107
|
-
# end #=>
|
52
|
+
# end #=> Fear.none()
|
108
53
|
#
|
109
|
-
# For(
|
110
|
-
# For(
|
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(
|
113
|
-
# For(
|
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 [
|
61
|
+
# @param monads [{#map, #flat_map}]
|
117
62
|
# @return [{#map, #flat_map}]
|
118
63
|
#
|
119
64
|
def For(*monads, &block)
|
120
|
-
|
65
|
+
Fear.for(*monads, &block)
|
121
66
|
end
|
122
67
|
end
|
123
68
|
end
|
data/lib/fear/for_api.rb
ADDED
@@ -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
|
data/lib/fear/future.rb
ADDED
@@ -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
|