fear 0.9.0 → 1.2.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 (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