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.
- checksums.yaml +5 -5
- data/.github/workflows/rubocop.yml +39 -0
- data/.github/workflows/spec.yml +42 -0
- data/.gitignore +0 -1
- data/.rubocop.yml +4 -12
- data/.simplecov +17 -0
- data/CHANGELOG.md +40 -0
- data/Gemfile +5 -2
- data/Gemfile.lock +130 -0
- data/LICENSE.txt +1 -1
- data/README.md +1293 -97
- data/Rakefile +369 -1
- 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 +17 -0
- data/examples/pattern_extracting_ruby2.7.rb +15 -0
- data/examples/pattern_matching_binary_tree_set.rb +101 -0
- data/examples/pattern_matching_number_in_words.rb +60 -0
- data/fear.gemspec +34 -23
- data/lib/dry/types/fear.rb +8 -0
- data/lib/dry/types/fear/option.rb +125 -0
- data/lib/fear.rb +65 -15
- data/lib/fear/await.rb +33 -0
- data/lib/fear/awaitable.rb +28 -0
- data/lib/fear/either.rb +131 -71
- data/lib/fear/either_api.rb +23 -0
- data/lib/fear/either_pattern_match.rb +53 -0
- data/lib/fear/empty_partial_function.rb +38 -0
- data/lib/fear/extractor.rb +112 -0
- data/lib/fear/extractor/anonymous_array_splat_matcher.rb +10 -0
- data/lib/fear/extractor/any_matcher.rb +17 -0
- data/lib/fear/extractor/array_head_matcher.rb +36 -0
- data/lib/fear/extractor/array_matcher.rb +40 -0
- data/lib/fear/extractor/array_splat_matcher.rb +16 -0
- data/lib/fear/extractor/empty_list_matcher.rb +20 -0
- data/lib/fear/extractor/extractor_matcher.rb +44 -0
- data/lib/fear/extractor/grammar.rb +203 -0
- data/lib/fear/extractor/grammar.treetop +129 -0
- data/lib/fear/extractor/identifier_matcher.rb +18 -0
- data/lib/fear/extractor/matcher.rb +53 -0
- data/lib/fear/extractor/matcher/and.rb +38 -0
- data/lib/fear/extractor/named_array_splat_matcher.rb +17 -0
- data/lib/fear/extractor/pattern.rb +58 -0
- data/lib/fear/extractor/typed_identifier_matcher.rb +26 -0
- data/lib/fear/extractor/value_matcher.rb +19 -0
- data/lib/fear/extractor_api.rb +35 -0
- data/lib/fear/failure.rb +46 -14
- data/lib/fear/failure_pattern_match.rb +10 -0
- data/lib/fear/for.rb +37 -95
- data/lib/fear/for_api.rb +68 -0
- data/lib/fear/future.rb +497 -0
- data/lib/fear/future_api.rb +21 -0
- data/lib/fear/left.rb +19 -2
- data/lib/fear/left_pattern_match.rb +11 -0
- data/lib/fear/none.rb +67 -3
- data/lib/fear/none_pattern_match.rb +14 -0
- data/lib/fear/option.rb +120 -56
- data/lib/fear/option_api.rb +40 -0
- data/lib/fear/option_pattern_match.rb +48 -0
- data/lib/fear/partial_function.rb +176 -0
- data/lib/fear/partial_function/and_then.rb +50 -0
- data/lib/fear/partial_function/any.rb +28 -0
- data/lib/fear/partial_function/combined.rb +53 -0
- data/lib/fear/partial_function/empty.rb +10 -0
- data/lib/fear/partial_function/guard.rb +80 -0
- data/lib/fear/partial_function/guard/and.rb +38 -0
- data/lib/fear/partial_function/guard/and3.rb +41 -0
- data/lib/fear/partial_function/guard/or.rb +38 -0
- data/lib/fear/partial_function/lifted.rb +23 -0
- data/lib/fear/partial_function/or_else.rb +64 -0
- data/lib/fear/partial_function_class.rb +38 -0
- data/lib/fear/pattern_match.rb +114 -0
- data/lib/fear/pattern_matching_api.rb +137 -0
- data/lib/fear/promise.rb +95 -0
- data/lib/fear/right.rb +20 -2
- data/lib/fear/right_biased.rb +6 -14
- data/lib/fear/right_pattern_match.rb +11 -0
- data/lib/fear/some.rb +55 -3
- data/lib/fear/some_pattern_match.rb +13 -0
- data/lib/fear/struct.rb +248 -0
- data/lib/fear/success.rb +35 -5
- data/lib/fear/success_pattern_match.rb +12 -0
- data/lib/fear/try.rb +136 -79
- data/lib/fear/try_api.rb +33 -0
- data/lib/fear/try_pattern_match.rb +33 -0
- data/lib/fear/unit.rb +32 -0
- data/lib/fear/utils.rb +39 -14
- data/lib/fear/version.rb +4 -1
- data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
- data/spec/dry/types/fear/option/core_spec.rb +77 -0
- data/spec/dry/types/fear/option/default_spec.rb +21 -0
- data/spec/dry/types/fear/option/hash_spec.rb +58 -0
- data/spec/dry/types/fear/option/option_spec.rb +97 -0
- data/spec/fear/awaitable_spec.rb +17 -0
- data/spec/fear/done_spec.rb +8 -6
- data/spec/fear/either/mixin_spec.rb +17 -0
- data/spec/fear/either_pattern_match_spec.rb +37 -0
- data/spec/fear/either_pattern_matching_spec.rb +28 -0
- data/spec/fear/extractor/array_matcher_spec.rb +230 -0
- data/spec/fear/extractor/extractor_matcher_spec.rb +153 -0
- data/spec/fear/extractor/grammar_array_spec.rb +25 -0
- data/spec/fear/extractor/identified_matcher_spec.rb +49 -0
- data/spec/fear/extractor/identifier_matcher_spec.rb +68 -0
- data/spec/fear/extractor/pattern_spec.rb +34 -0
- data/spec/fear/extractor/typed_identifier_matcher_spec.rb +64 -0
- data/spec/fear/extractor/value_matcher_number_spec.rb +79 -0
- data/spec/fear/extractor/value_matcher_string_spec.rb +88 -0
- data/spec/fear/extractor/value_matcher_symbol_spec.rb +71 -0
- data/spec/fear/extractor_api_spec.rb +115 -0
- data/spec/fear/extractor_spec.rb +61 -0
- data/spec/fear/failure_spec.rb +145 -45
- data/spec/fear/for_spec.rb +57 -67
- data/spec/fear/future_spec.rb +691 -0
- data/spec/fear/guard_spec.rb +103 -0
- data/spec/fear/left_spec.rb +112 -46
- data/spec/fear/none_spec.rb +114 -16
- data/spec/fear/option/mixin_spec.rb +39 -0
- data/spec/fear/option_pattern_match_spec.rb +35 -0
- data/spec/fear/option_pattern_matching_spec.rb +34 -0
- data/spec/fear/option_spec.rb +121 -8
- data/spec/fear/partial_function/empty_spec.rb +38 -0
- data/spec/fear/partial_function_and_then_spec.rb +147 -0
- data/spec/fear/partial_function_composition_spec.rb +82 -0
- data/spec/fear/partial_function_or_else_spec.rb +276 -0
- data/spec/fear/partial_function_spec.rb +239 -0
- data/spec/fear/pattern_match_spec.rb +93 -0
- data/spec/fear/pattern_matching_api_spec.rb +31 -0
- data/spec/fear/promise_spec.rb +96 -0
- data/spec/fear/right_biased/left.rb +29 -32
- data/spec/fear/right_biased/right.rb +51 -54
- data/spec/fear/right_spec.rb +109 -41
- data/spec/fear/some_spec.rb +80 -15
- data/spec/fear/success_spec.rb +99 -32
- data/spec/fear/try/mixin_spec.rb +19 -0
- data/spec/fear/try_pattern_match_spec.rb +37 -0
- data/spec/fear/try_pattern_matching_spec.rb +34 -0
- data/spec/fear/utils_spec.rb +16 -14
- data/spec/spec_helper.rb +13 -7
- data/spec/struct_pattern_matching_spec.rb +36 -0
- data/spec/struct_spec.rb +226 -0
- data/spec/support/dry_types.rb +6 -0
- metadata +320 -29
- data/.travis.yml +0 -9
- data/lib/fear/done.rb +0 -22
- data/lib/fear/for/evaluation_context.rb +0 -91
data/lib/fear/for.rb
CHANGED
@@ -1,96 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
|
-
#
|
3
|
-
#
|
4
|
-
|
5
|
-
|
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
|
9
|
+
# @param monads [<Fear::Option, Fear::Either, Fear::Try, Proc>]
|
60
10
|
#
|
61
|
-
def
|
62
|
-
|
63
|
-
@evaluation_context = EvaluationContext.new(outer_context)
|
64
|
-
end
|
11
|
+
def call(monads, inner_values = [], &block)
|
12
|
+
head, *tail = *monads
|
65
13
|
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
74
|
-
|
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
|
77
|
-
|
78
|
-
|
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
|
-
|
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(
|
104
|
-
# For(
|
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(
|
48
|
+
# For(proc { Fear.some(2) }, proc { Fear.some(3) }) do |a, b|
|
107
49
|
# a * b
|
108
|
-
# end #=>
|
50
|
+
# end #=> Fear.some(6)
|
109
51
|
#
|
110
|
-
# For(
|
52
|
+
# For(proc { Fear.none() }, proc { raise }) do |a, b|
|
111
53
|
# a * b
|
112
|
-
# end #=>
|
54
|
+
# end #=> Fear.none()
|
113
55
|
#
|
114
|
-
# For(
|
115
|
-
# For(
|
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(
|
118
|
-
# For(
|
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
|
63
|
+
# @param monads [{#map, #flat_map}]
|
122
64
|
# @return [{#map, #flat_map}]
|
123
65
|
#
|
124
|
-
def For(
|
125
|
-
|
66
|
+
def For(*monads, &block)
|
67
|
+
Fear.for(*monads, &block)
|
126
68
|
end
|
127
69
|
end
|
128
70
|
end
|
data/lib/fear/for_api.rb
ADDED
@@ -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
|
data/lib/fear/future.rb
ADDED
@@ -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
|