fear 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +27 -0
- data/.github/workflows/rubocop.yml +39 -0
- data/.github/workflows/spec.yml +42 -0
- data/.rubocop.yml +4 -60
- data/.simplecov +17 -0
- data/CHANGELOG.md +29 -1
- data/Gemfile +5 -5
- data/Gemfile.lock +86 -50
- data/README.md +240 -209
- data/Rakefile +72 -65
- data/examples/pattern_extracting.rb +10 -8
- data/examples/pattern_matching_binary_tree_set.rb +7 -2
- data/examples/pattern_matching_number_in_words.rb +48 -42
- data/fear.gemspec +33 -34
- data/lib/dry/types/fear/option.rb +125 -0
- data/lib/dry/types/fear.rb +8 -0
- data/lib/fear/await.rb +33 -0
- data/lib/fear/awaitable.rb +28 -0
- data/lib/fear/either.rb +15 -4
- data/lib/fear/either_api.rb +4 -0
- data/lib/fear/either_pattern_match.rb +9 -5
- data/lib/fear/empty_partial_function.rb +3 -1
- data/lib/fear/failure.rb +7 -7
- data/lib/fear/failure_pattern_match.rb +4 -0
- data/lib/fear/for.rb +4 -2
- data/lib/fear/for_api.rb +5 -1
- data/lib/fear/future.rb +157 -82
- data/lib/fear/future_api.rb +17 -4
- data/lib/fear/left.rb +3 -9
- data/lib/fear/left_pattern_match.rb +2 -0
- data/lib/fear/none.rb +28 -10
- data/lib/fear/none_pattern_match.rb +2 -0
- data/lib/fear/option.rb +30 -2
- data/lib/fear/option_api.rb +4 -0
- data/lib/fear/option_pattern_match.rb +8 -3
- data/lib/fear/partial_function/and_then.rb +4 -2
- data/lib/fear/partial_function/any.rb +2 -0
- data/lib/fear/partial_function/combined.rb +3 -1
- data/lib/fear/partial_function/empty.rb +6 -0
- data/lib/fear/partial_function/guard/and.rb +2 -0
- data/lib/fear/partial_function/guard/and3.rb +2 -0
- data/lib/fear/partial_function/guard/or.rb +2 -0
- data/lib/fear/partial_function/guard.rb +8 -6
- data/lib/fear/partial_function/lifted.rb +2 -0
- data/lib/fear/partial_function/or_else.rb +5 -1
- data/lib/fear/partial_function.rb +18 -9
- data/lib/fear/partial_function_class.rb +3 -1
- data/lib/fear/pattern_match.rb +3 -11
- data/lib/fear/pattern_matching_api.rb +6 -28
- data/lib/fear/promise.rb +7 -5
- data/lib/fear/right.rb +3 -9
- data/lib/fear/right_biased.rb +5 -3
- data/lib/fear/right_pattern_match.rb +4 -0
- data/lib/fear/some.rb +35 -8
- data/lib/fear/some_pattern_match.rb +2 -0
- data/lib/fear/struct.rb +237 -0
- data/lib/fear/success.rb +7 -8
- data/lib/fear/success_pattern_match.rb +4 -0
- data/lib/fear/try.rb +8 -2
- data/lib/fear/try_api.rb +4 -0
- data/lib/fear/try_pattern_match.rb +9 -5
- data/lib/fear/unit.rb +6 -2
- data/lib/fear/utils.rb +14 -2
- data/lib/fear/version.rb +4 -1
- data/lib/fear.rb +26 -44
- 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 +19 -0
- data/spec/fear/done_spec.rb +7 -5
- data/spec/fear/either/mixin_spec.rb +4 -2
- data/spec/fear/either_pattern_match_spec.rb +10 -8
- data/spec/fear/either_pattern_matching_spec.rb +28 -0
- data/spec/fear/either_spec.rb +26 -0
- data/spec/fear/failure_spec.rb +57 -70
- data/spec/fear/for/mixin_spec.rb +15 -0
- data/spec/fear/for_spec.rb +19 -17
- data/spec/fear/future_spec.rb +477 -237
- data/spec/fear/guard_spec.rb +136 -24
- data/spec/fear/left_spec.rb +57 -70
- data/spec/fear/none_spec.rb +39 -43
- data/spec/fear/option/mixin_spec.rb +9 -7
- data/spec/fear/option_pattern_match_spec.rb +10 -8
- data/spec/fear/option_pattern_matching_spec.rb +34 -0
- data/spec/fear/option_spec.rb +142 -0
- data/spec/fear/partial_function/any_spec.rb +25 -0
- data/spec/fear/partial_function/empty_spec.rb +12 -10
- data/spec/fear/partial_function_and_then_spec.rb +39 -37
- data/spec/fear/partial_function_composition_spec.rb +46 -44
- data/spec/fear/partial_function_or_else_spec.rb +92 -90
- data/spec/fear/partial_function_spec.rb +91 -61
- data/spec/fear/pattern_match_spec.rb +19 -51
- data/spec/fear/pattern_matching_api_spec.rb +31 -0
- data/spec/fear/promise_spec.rb +23 -23
- data/spec/fear/right_biased/left.rb +28 -26
- data/spec/fear/right_biased/right.rb +51 -49
- data/spec/fear/right_spec.rb +48 -68
- data/spec/fear/some_spec.rb +30 -40
- data/spec/fear/success_spec.rb +40 -60
- data/spec/fear/try/mixin_spec.rb +19 -3
- data/spec/fear/try_api_spec.rb +23 -0
- data/spec/fear/try_pattern_match_spec.rb +10 -8
- 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 +194 -0
- data/spec/support/dry_types.rb +6 -0
- metadata +128 -87
- data/.travis.yml +0 -13
- data/lib/fear/extractor/anonymous_array_splat_matcher.rb +0 -8
- data/lib/fear/extractor/any_matcher.rb +0 -15
- data/lib/fear/extractor/array_head_matcher.rb +0 -34
- data/lib/fear/extractor/array_matcher.rb +0 -38
- data/lib/fear/extractor/array_splat_matcher.rb +0 -14
- data/lib/fear/extractor/empty_list_matcher.rb +0 -18
- data/lib/fear/extractor/extractor_matcher.rb +0 -42
- data/lib/fear/extractor/grammar.rb +0 -201
- data/lib/fear/extractor/grammar.treetop +0 -129
- data/lib/fear/extractor/identifier_matcher.rb +0 -16
- data/lib/fear/extractor/matcher/and.rb +0 -36
- data/lib/fear/extractor/matcher.rb +0 -54
- data/lib/fear/extractor/named_array_splat_matcher.rb +0 -15
- data/lib/fear/extractor/pattern.rb +0 -55
- data/lib/fear/extractor/typed_identifier_matcher.rb +0 -24
- data/lib/fear/extractor/value_matcher.rb +0 -17
- data/lib/fear/extractor.rb +0 -108
- data/lib/fear/extractor_api.rb +0 -33
- data/spec/fear/extractor/array_matcher_spec.rb +0 -228
- data/spec/fear/extractor/extractor_matcher_spec.rb +0 -151
- data/spec/fear/extractor/grammar_array_spec.rb +0 -23
- data/spec/fear/extractor/identified_matcher_spec.rb +0 -47
- data/spec/fear/extractor/identifier_matcher_spec.rb +0 -66
- data/spec/fear/extractor/pattern_spec.rb +0 -32
- data/spec/fear/extractor/typed_identifier_matcher_spec.rb +0 -62
- data/spec/fear/extractor/value_matcher_number_spec.rb +0 -77
- data/spec/fear/extractor/value_matcher_string_spec.rb +0 -86
- data/spec/fear/extractor/value_matcher_symbol_spec.rb +0 -69
- data/spec/fear/extractor_api_spec.rb +0 -113
- data/spec/fear/extractor_spec.rb +0 -59
data/lib/fear/future.rb
CHANGED
@@ -1,20 +1,14 @@
|
|
1
|
-
|
2
|
-
require 'concurrent'
|
3
|
-
rescue LoadError
|
4
|
-
puts "You must add 'concurrent-ruby' to your Gemfile in order to use Fear::Future"
|
5
|
-
end
|
6
|
-
require_relative 'promise'
|
1
|
+
# frozen_string_literal: true
|
7
2
|
|
8
3
|
module Fear
|
9
4
|
# Asynchronous computations that yield futures are created
|
10
5
|
# with the +Fear.future+ call:
|
11
6
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# end
|
7
|
+
# success = "Hello"
|
8
|
+
# f = Fear.future { success + ' future!' }
|
9
|
+
# f.on_success do |result|
|
10
|
+
# puts result
|
11
|
+
# end
|
18
12
|
#
|
19
13
|
# Multiple callbacks may be registered; there is no guarantee
|
20
14
|
# that they will be executed in a particular order.
|
@@ -23,30 +17,45 @@ module Fear
|
|
23
17
|
# that the future failed. Futures obtained through combinators
|
24
18
|
# have the same error as the future they were obtained from.
|
25
19
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
20
|
+
# f = Fear.future { 5 }
|
21
|
+
# g = Fear.future { 3 }
|
22
|
+
# f.flat_map do |x|
|
23
|
+
# g.map { |y| x + y }
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# The same program may be written using +Fear.for+
|
27
|
+
#
|
28
|
+
# Fear.for(Fear.future { 5 }, Fear.future { 3 }) do |x, y|
|
29
|
+
# x + y
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# Futures use +Concurrent::Promise+ under the hood. +Fear.future+ accepts optional configuration Hash passed
|
33
|
+
# directly to underlying promise. For example, run it on custom thread pool.
|
32
34
|
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
35
|
+
# require 'open-uri'
|
36
|
+
#
|
37
|
+
# future = Fear.future(executor: :io) { open('https://example.com/') }
|
38
|
+
#
|
39
|
+
# future.map(executor: :fast, &:read).each do |body|
|
40
|
+
# puts "#{body}"
|
41
|
+
# end
|
37
42
|
#
|
38
43
|
# @see https://github.com/scala/scala/blob/2.11.x/src/library/scala/concurrent/Future.scala
|
39
44
|
#
|
40
45
|
class Future
|
46
|
+
include Awaitable
|
47
|
+
|
41
48
|
# @param promise [nil, Concurrent::Future] converts
|
42
|
-
# +Concurrent::
|
49
|
+
# +Concurrent::Promise+ into +Fear::Future+.
|
43
50
|
# @param options [see Concurrent::Future] options will be passed
|
44
|
-
# directly to +Concurrent::
|
51
|
+
# directly to +Concurrent::Promise+
|
45
52
|
# @yield given block and evaluate it in the future.
|
46
53
|
# @api private
|
54
|
+
# @see Fear.future
|
55
|
+
#
|
47
56
|
def initialize(promise = nil, **options, &block)
|
48
57
|
if block_given? && promise
|
49
|
-
raise ArgumentError,
|
58
|
+
raise ArgumentError, "pass block or promise"
|
50
59
|
end
|
51
60
|
|
52
61
|
@options = options
|
@@ -57,11 +66,11 @@ module Fear
|
|
57
66
|
attr_reader :promise
|
58
67
|
private :promise
|
59
68
|
|
60
|
-
# Calls the provided callback
|
69
|
+
# Calls the provided callback when this future is completed successfully.
|
61
70
|
#
|
62
71
|
# If the future has already been completed with a value,
|
63
72
|
# this will either be applied immediately or be scheduled asynchronously.
|
64
|
-
# @yieldparam [value
|
73
|
+
# @yieldparam [any] value
|
65
74
|
# @return [self]
|
66
75
|
# @see #transform
|
67
76
|
#
|
@@ -76,6 +85,24 @@ module Fear
|
|
76
85
|
end
|
77
86
|
end
|
78
87
|
|
88
|
+
# When this future is completed successfully match against its result
|
89
|
+
#
|
90
|
+
# If the future has already been completed with a value,
|
91
|
+
# this will either be applied immediately or be scheduled asynchronously.
|
92
|
+
# @yieldparam [Fear::PatternMatch] m
|
93
|
+
# @return [self]
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
# Fear.future { }.on_success_match do |m|
|
97
|
+
# m.case(42) { ... }
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
def on_success_match
|
101
|
+
on_success do |value|
|
102
|
+
Fear.matcher { |m| yield(m) }.call_or_else(value, &:itself)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
79
106
|
# When this future is completed with a failure apply the provided callback to the error.
|
80
107
|
#
|
81
108
|
# If the future has already been completed with a failure,
|
@@ -100,6 +127,26 @@ module Fear
|
|
100
127
|
end
|
101
128
|
end
|
102
129
|
|
130
|
+
# When this future is completed with a failure match against the error.
|
131
|
+
#
|
132
|
+
# If the future has already been completed with a failure,
|
133
|
+
# this will either be applied immediately or be scheduled asynchronously.
|
134
|
+
#
|
135
|
+
# Will not be called in case that the future is completed with a value.
|
136
|
+
# @yieldparam [Fear::PatternMatch] m
|
137
|
+
# @return [self]
|
138
|
+
#
|
139
|
+
# @example
|
140
|
+
# Fear.future { }.on_failure_match do |m|
|
141
|
+
# m.case(HTTPError) { |error| ... }
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
def on_failure_match
|
145
|
+
on_failure do |error|
|
146
|
+
Fear.matcher { |m| yield(m) }.call_or_else(error, &:itself)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
103
150
|
# When this future is completed call the provided block.
|
104
151
|
#
|
105
152
|
# If the future has already been completed,
|
@@ -119,6 +166,26 @@ module Fear
|
|
119
166
|
self
|
120
167
|
end
|
121
168
|
|
169
|
+
# When this future is completed match against result.
|
170
|
+
#
|
171
|
+
# If the future has already been completed,
|
172
|
+
# this will either be applied immediately or be scheduled asynchronously.
|
173
|
+
# @yieldparam [Fear::TryPatternMatch]
|
174
|
+
# @return [self]
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# Fear.future { }.on_complete_match do |m|
|
178
|
+
# m.success { |result| }
|
179
|
+
# m.failure { |error| }
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
def on_complete_match
|
183
|
+
promise.add_observer do |_time, try, _error|
|
184
|
+
Fear::Try.matcher { |m| yield(m) }.call_or_else(try, &:itself)
|
185
|
+
end
|
186
|
+
self
|
187
|
+
end
|
188
|
+
|
122
189
|
# Returns whether the future has already been completed with
|
123
190
|
# a value or an error.
|
124
191
|
#
|
@@ -176,12 +243,10 @@ module Fear
|
|
176
243
|
# )
|
177
244
|
#
|
178
245
|
def transform(success, failure)
|
179
|
-
promise = Promise.new(
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
m.failure { |error| promise.failure(failure.call(error)) }
|
184
|
-
end
|
246
|
+
promise = Promise.new(**@options)
|
247
|
+
on_complete_match do |m|
|
248
|
+
m.success { |value| promise.success(success.(value)) }
|
249
|
+
m.failure { |error| promise.failure(failure.(error)) }
|
185
250
|
end
|
186
251
|
promise.to_future
|
187
252
|
end
|
@@ -197,7 +262,7 @@ module Fear
|
|
197
262
|
# future.map { |v| v * 2 } #=> the same as Fear.future { 2 * 2 }
|
198
263
|
#
|
199
264
|
def map(&block)
|
200
|
-
promise = Promise.new(
|
265
|
+
promise = Promise.new(**@options)
|
201
266
|
on_complete do |try|
|
202
267
|
promise.complete!(try.map(&block))
|
203
268
|
end
|
@@ -222,18 +287,14 @@ module Fear
|
|
222
287
|
# end
|
223
288
|
# end
|
224
289
|
#
|
225
|
-
def flat_map
|
226
|
-
promise = Promise.new(
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
rescue StandardError => error
|
234
|
-
promise.failure!(error)
|
235
|
-
end
|
236
|
-
end
|
290
|
+
def flat_map
|
291
|
+
promise = Promise.new(**@options)
|
292
|
+
on_complete_match do |m|
|
293
|
+
m.case(Fear::Failure) { |failure| promise.complete!(failure) }
|
294
|
+
m.success do |value|
|
295
|
+
yield(value).on_complete { |callback_result| promise.complete!(callback_result) }
|
296
|
+
rescue StandardError => error
|
297
|
+
promise.failure!(error)
|
237
298
|
end
|
238
299
|
end
|
239
300
|
promise.to_future
|
@@ -261,7 +322,7 @@ module Fear
|
|
261
322
|
if yield(result)
|
262
323
|
result
|
263
324
|
else
|
264
|
-
raise NoSuchElementError,
|
325
|
+
raise NoSuchElementError, "#select predicate is not satisfied"
|
265
326
|
end
|
266
327
|
end
|
267
328
|
end
|
@@ -281,7 +342,7 @@ module Fear
|
|
281
342
|
#
|
282
343
|
#
|
283
344
|
def recover(&block)
|
284
|
-
promise = Promise.new(
|
345
|
+
promise = Promise.new(**@options)
|
285
346
|
on_complete do |try|
|
286
347
|
promise.complete!(try.recover(&block))
|
287
348
|
end
|
@@ -306,19 +367,25 @@ module Fear
|
|
306
367
|
# future1.zip(future2) #=> returns the same result as Fear.future { [call_service1, call_service2] },
|
307
368
|
# # but it performs two calls asynchronously
|
308
369
|
#
|
309
|
-
def zip(other)
|
310
|
-
promise = Promise.new(
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
370
|
+
def zip(other)
|
371
|
+
promise = Promise.new(**@options)
|
372
|
+
on_complete_match do |m|
|
373
|
+
m.success do |value|
|
374
|
+
other.on_complete do |other_try|
|
375
|
+
promise.complete!(
|
376
|
+
other_try.map do |other_value|
|
377
|
+
if block_given?
|
378
|
+
yield(value, other_value)
|
379
|
+
else
|
380
|
+
[value, other_value]
|
381
|
+
end
|
382
|
+
end,
|
383
|
+
)
|
320
384
|
end
|
321
385
|
end
|
386
|
+
m.failure do |error|
|
387
|
+
promise.failure!(error)
|
388
|
+
end
|
322
389
|
end
|
323
390
|
|
324
391
|
promise.to_future
|
@@ -338,26 +405,20 @@ module Fear
|
|
338
405
|
# g = Fear.future { 5 }
|
339
406
|
# f.fallback_to(g) # evaluates to 5
|
340
407
|
#
|
341
|
-
# rubocop: disable Metrics/MethodLength
|
342
408
|
def fallback_to(fallback)
|
343
|
-
promise = Promise.new(
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
m2.success { |value| promise.complete!(value) }
|
351
|
-
m2.failure { promise.failure!(error) }
|
352
|
-
end
|
353
|
-
end
|
409
|
+
promise = Promise.new(**@options)
|
410
|
+
on_complete_match do |m|
|
411
|
+
m.success { |value| promise.complete!(value) }
|
412
|
+
m.failure do |error|
|
413
|
+
fallback.on_complete_match do |m2|
|
414
|
+
m2.success { |value| promise.complete!(value) }
|
415
|
+
m2.failure { promise.failure!(error) }
|
354
416
|
end
|
355
417
|
end
|
356
418
|
end
|
357
419
|
|
358
420
|
promise.to_future
|
359
421
|
end
|
360
|
-
# rubocop: enable Metrics/MethodLength
|
361
422
|
|
362
423
|
# Applies the side-effecting block to the result of +self+ future,
|
363
424
|
# and returns a new future with the result of this future.
|
@@ -373,31 +434,46 @@ module Fear
|
|
373
434
|
# @example The following example prints out +5+:
|
374
435
|
# f = Fear.future { 5 }
|
375
436
|
# f.and_then do
|
376
|
-
# fail 'runtime error'
|
437
|
+
# m.success { }fail| 'runtime error' }
|
377
438
|
# end.and_then do |m|
|
378
439
|
# m.success { |value| puts value } # it evaluates this branch
|
379
440
|
# m.failure { |error| puts error.massage }
|
380
441
|
# end
|
381
442
|
#
|
382
|
-
def and_then
|
383
|
-
promise = Promise.new(
|
443
|
+
def and_then
|
444
|
+
promise = Promise.new(**@options)
|
384
445
|
on_complete do |try|
|
385
|
-
Fear.try
|
446
|
+
Fear.try do
|
447
|
+
Fear::Try.matcher { |m| yield(m) }.call_or_else(try, &:itself)
|
448
|
+
end
|
386
449
|
promise.complete!(try)
|
387
450
|
end
|
388
451
|
|
389
452
|
promise.to_future
|
390
453
|
end
|
391
454
|
|
455
|
+
# @api private
|
456
|
+
def __result__(at_most)
|
457
|
+
__ready__(at_most).value.get_or_else { raise "promise not completed" }
|
458
|
+
end
|
459
|
+
|
460
|
+
# @api private
|
461
|
+
def __ready__(at_most)
|
462
|
+
if promise.wait(at_most).complete?
|
463
|
+
self
|
464
|
+
else
|
465
|
+
raise Timeout::Error
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
392
469
|
class << self
|
393
470
|
# Creates an already completed +Future+ with the specified error.
|
394
471
|
# @param exception [StandardError]
|
395
472
|
# @return [Fear::Future]
|
396
473
|
#
|
397
474
|
def failed(exception)
|
398
|
-
new
|
399
|
-
|
400
|
-
end
|
475
|
+
new { raise exception }
|
476
|
+
.yield_self { |future| Fear::Await.ready(future, 10) }
|
401
477
|
end
|
402
478
|
|
403
479
|
# Creates an already completed +Future+ with the specified result.
|
@@ -405,9 +481,8 @@ module Fear
|
|
405
481
|
# @return [Fear::Future]
|
406
482
|
#
|
407
483
|
def successful(result)
|
408
|
-
new
|
409
|
-
|
410
|
-
end
|
484
|
+
new { result }
|
485
|
+
.yield_self { |future| Fear::Await.ready(future, 10) }
|
411
486
|
end
|
412
487
|
end
|
413
488
|
end
|
data/lib/fear/future_api.rb
CHANGED
@@ -1,5 +1,18 @@
|
|
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
|
+
require "fear/awaitable"
|
10
|
+
require "fear/await"
|
11
|
+
require "fear/future"
|
12
|
+
require "fear/promise"
|
13
|
+
|
1
14
|
module Fear
|
2
|
-
# rubocop: disable
|
15
|
+
# rubocop: disable Layout/LineLength
|
3
16
|
module FutureApi
|
4
17
|
# Asynchronously evaluates the block
|
5
18
|
# @param options [Hash] options will be passed directly to underlying +Concurrent::Promise+
|
@@ -11,9 +24,9 @@ module Fear
|
|
11
24
|
# f = Fear.future(executor: :io) { open('http://example.com') }
|
12
25
|
# f.map(&:read).each { |body| puts body }
|
13
26
|
#
|
14
|
-
def future(options
|
15
|
-
Future.new(options, &block)
|
27
|
+
def future(**options, &block)
|
28
|
+
Future.new(nil, **options, &block)
|
16
29
|
end
|
17
30
|
end
|
18
|
-
# rubocop: enable
|
31
|
+
# rubocop: enable Layout/LineLength
|
19
32
|
end
|
data/lib/fear/left.rb
CHANGED
@@ -1,17 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
class Left
|
3
5
|
include Either
|
4
6
|
include RightBiased::Left
|
5
7
|
include LeftPatternMatch.mixin
|
6
8
|
|
7
|
-
EXTRACTOR = proc do |either|
|
8
|
-
if Fear::Left === either
|
9
|
-
Fear.some([either.left_value])
|
10
|
-
else
|
11
|
-
Fear.none
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
9
|
# @api private
|
16
10
|
def left_value
|
17
11
|
value
|
@@ -52,7 +46,7 @@ module Fear
|
|
52
46
|
# @param reduce_left [Proc]
|
53
47
|
# @return [any]
|
54
48
|
def reduce(reduce_left, _reduce_right)
|
55
|
-
reduce_left.
|
49
|
+
reduce_left.(value)
|
56
50
|
end
|
57
51
|
|
58
52
|
# @return [self]
|
data/lib/fear/none.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
# @api private
|
3
5
|
class NoneClass
|
@@ -5,14 +7,6 @@ module Fear
|
|
5
7
|
include RightBiased::Left
|
6
8
|
include NonePatternMatch.mixin
|
7
9
|
|
8
|
-
EXTRACTOR = proc do |option|
|
9
|
-
if Fear::None === option
|
10
|
-
Fear.some([])
|
11
|
-
else
|
12
|
-
Fear.none
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
10
|
# @raise [NoSuchElementError]
|
17
11
|
def get
|
18
12
|
raise NoSuchElementError
|
@@ -28,6 +22,13 @@ module Fear
|
|
28
22
|
true
|
29
23
|
end
|
30
24
|
|
25
|
+
alias :blank? :empty?
|
26
|
+
|
27
|
+
# @return [false]
|
28
|
+
def present?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
31
32
|
# @return [None]
|
32
33
|
def select(*)
|
33
34
|
self
|
@@ -40,7 +41,7 @@ module Fear
|
|
40
41
|
|
41
42
|
# @return [String]
|
42
43
|
def inspect
|
43
|
-
|
44
|
+
"#<Fear::NoneClass>"
|
44
45
|
end
|
45
46
|
|
46
47
|
# @return [String]
|
@@ -57,12 +58,29 @@ module Fear
|
|
57
58
|
def ===(other)
|
58
59
|
self == other
|
59
60
|
end
|
61
|
+
|
62
|
+
# @param other [Fear::Option]
|
63
|
+
# @return [Fear::Option]
|
64
|
+
def zip(other)
|
65
|
+
if other.is_a?(Option)
|
66
|
+
Fear.none
|
67
|
+
else
|
68
|
+
raise TypeError, "can't zip with #{other.class}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [RightBiased::Left]
|
73
|
+
def filter_map
|
74
|
+
self
|
75
|
+
end
|
60
76
|
end
|
61
77
|
|
62
78
|
private_constant(:NoneClass)
|
63
79
|
|
64
80
|
# The only instance of NoneClass
|
81
|
+
# @api private
|
65
82
|
None = NoneClass.new.freeze
|
83
|
+
public_constant :None
|
66
84
|
|
67
85
|
class << NoneClass
|
68
86
|
def new
|
@@ -70,7 +88,7 @@ module Fear
|
|
70
88
|
end
|
71
89
|
|
72
90
|
def inherited(*)
|
73
|
-
raise
|
91
|
+
raise "you are not allowed to inherit from NoneClass, use Fear::None instead"
|
74
92
|
end
|
75
93
|
end
|
76
94
|
end
|
data/lib/fear/option.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
# Represents optional values. Instances of +Option+
|
3
5
|
# are either an instance of +Some+ or the object +None+.
|
@@ -80,6 +82,17 @@ module Fear
|
|
80
82
|
# Fear.some(42).map { |v| v/2 } #=> Fear.some(21)
|
81
83
|
# Fear.none.map { |v| v/2 } #=> None
|
82
84
|
#
|
85
|
+
# @!method filter_map(&block)
|
86
|
+
# Returns a new +Some+ of truthy results (everything except +false+ or +nil+) of
|
87
|
+
# running the block or +None+ otherwise.
|
88
|
+
# @yieldparam [any] value
|
89
|
+
# @yieldreturn [any]
|
90
|
+
# @example
|
91
|
+
# Fear.some(42).filter_map { |v| v/2 if v.even? } #=> Fear.some(21)
|
92
|
+
# Fear.some(42).filter_map { |v| v/2 if v.odd? } #=> Fear.none
|
93
|
+
# Fear.some(42).filter_map { |v| false } #=> Fear.none
|
94
|
+
# Fear.none.filter_map { |v| v/2 } #=> Fear.none
|
95
|
+
#
|
83
96
|
# @!method flat_map(&block)
|
84
97
|
# Returns the given block applied to the value from this +Some+
|
85
98
|
# or returns this if this is a +None+
|
@@ -108,7 +121,7 @@ module Fear
|
|
108
121
|
# @yieldreturn [Boolean]
|
109
122
|
# @return [Option]
|
110
123
|
# @example
|
111
|
-
# Fear.some(42).select { |v| v > 40 } #=> Fear.success(
|
124
|
+
# Fear.some(42).select { |v| v > 40 } #=> Fear.success(42)
|
112
125
|
# Fear.some(42).select { |v| v < 40 } #=> None
|
113
126
|
# Fear.none.select { |v| v < 40 } #=> None
|
114
127
|
#
|
@@ -151,6 +164,17 @@ module Fear
|
|
151
164
|
# m.else { 'error '}
|
152
165
|
# end
|
153
166
|
#
|
167
|
+
# @!method zip(other)
|
168
|
+
# @param other [Fear::Option]
|
169
|
+
# @return [Fear::Option] a +Fear::Some+ formed from this option and another option by
|
170
|
+
# combining the corresponding elements in an array.
|
171
|
+
#
|
172
|
+
# @example
|
173
|
+
# Fear.some("foo").zip(Fear.some("bar")) #=> Fear.some(["foo", "bar"])
|
174
|
+
# Fear.some("foo").zip(Fear.some("bar")) { |x, y| x + y } #=> Fear.some("foobar")
|
175
|
+
# Fear.some("foo").zip(Fear.none) #=> Fear.none
|
176
|
+
# Fear.none.zip(Fear.some("bar")) #=> Fear.none
|
177
|
+
#
|
154
178
|
# @see https://github.com/scala/scala/blob/2.11.x/src/library/scala/Option.scala
|
155
179
|
#
|
156
180
|
module Option
|
@@ -187,7 +211,7 @@ module Fear
|
|
187
211
|
end
|
188
212
|
|
189
213
|
def match(value, &block)
|
190
|
-
matcher(&block).
|
214
|
+
matcher(&block).(value)
|
191
215
|
end
|
192
216
|
end
|
193
217
|
|
@@ -234,3 +258,7 @@ module Fear
|
|
234
258
|
end
|
235
259
|
end
|
236
260
|
end
|
261
|
+
|
262
|
+
require "fear/option_pattern_match"
|
263
|
+
require "fear/some"
|
264
|
+
require "fear/none"
|
data/lib/fear/option_api.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fear/pattern_match"
|
4
|
+
|
1
5
|
module Fear
|
2
6
|
# Option pattern matcher
|
3
7
|
#
|
@@ -22,14 +26,12 @@ module Fear
|
|
22
26
|
# @note it has two optimized subclasses +Fear::SomePatternMatch+ and +Fear::NonePatternMatch+
|
23
27
|
# @api private
|
24
28
|
class OptionPatternMatch < Fear::PatternMatch
|
25
|
-
GET_METHOD = :get.to_proc
|
26
|
-
|
27
29
|
# Match against Some
|
28
30
|
#
|
29
31
|
# @param conditions [<#==>]
|
30
32
|
# @return [Fear::OptionPatternMatch]
|
31
33
|
def some(*conditions, &effect)
|
32
|
-
branch = Fear.case(Fear::Some,
|
34
|
+
branch = Fear.case(Fear::Some, &:get).and_then(Fear.case(*conditions, &effect))
|
33
35
|
or_else(branch)
|
34
36
|
end
|
35
37
|
|
@@ -43,3 +45,6 @@ module Fear
|
|
43
45
|
end
|
44
46
|
end
|
45
47
|
end
|
48
|
+
|
49
|
+
require "fear/some_pattern_match"
|
50
|
+
require "fear/none_pattern_match"
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
module PartialFunction
|
3
5
|
# Composite function produced by +PartialFunction#and_then+ method
|
@@ -23,7 +25,7 @@ module Fear
|
|
23
25
|
# @param arg [any]
|
24
26
|
# @return [any ]
|
25
27
|
def call(arg)
|
26
|
-
function.
|
28
|
+
function.(partial_function.(arg))
|
27
29
|
end
|
28
30
|
|
29
31
|
# @param arg [any]
|
@@ -39,7 +41,7 @@ module Fear
|
|
39
41
|
result = partial_function.call_or_else(arg) do
|
40
42
|
return yield(arg)
|
41
43
|
end
|
42
|
-
function.
|
44
|
+
function.(result)
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
module PartialFunction
|
3
5
|
# Composite function produced by +PartialFunction#and_then+ method
|
@@ -22,7 +24,7 @@ module Fear
|
|
22
24
|
# @param arg [any]
|
23
25
|
# @return [any ]
|
24
26
|
def call(arg)
|
25
|
-
f2.
|
27
|
+
f2.(f1.(arg))
|
26
28
|
end
|
27
29
|
|
28
30
|
alias === call
|