fear 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|