fear 0.9.0 → 1.2.0

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