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.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +27 -0
  3. data/.github/workflows/rubocop.yml +39 -0
  4. data/.github/workflows/spec.yml +42 -0
  5. data/.rubocop.yml +4 -60
  6. data/.simplecov +17 -0
  7. data/CHANGELOG.md +29 -1
  8. data/Gemfile +5 -5
  9. data/Gemfile.lock +86 -50
  10. data/README.md +240 -209
  11. data/Rakefile +72 -65
  12. data/examples/pattern_extracting.rb +10 -8
  13. data/examples/pattern_matching_binary_tree_set.rb +7 -2
  14. data/examples/pattern_matching_number_in_words.rb +48 -42
  15. data/fear.gemspec +33 -34
  16. data/lib/dry/types/fear/option.rb +125 -0
  17. data/lib/dry/types/fear.rb +8 -0
  18. data/lib/fear/await.rb +33 -0
  19. data/lib/fear/awaitable.rb +28 -0
  20. data/lib/fear/either.rb +15 -4
  21. data/lib/fear/either_api.rb +4 -0
  22. data/lib/fear/either_pattern_match.rb +9 -5
  23. data/lib/fear/empty_partial_function.rb +3 -1
  24. data/lib/fear/failure.rb +7 -7
  25. data/lib/fear/failure_pattern_match.rb +4 -0
  26. data/lib/fear/for.rb +4 -2
  27. data/lib/fear/for_api.rb +5 -1
  28. data/lib/fear/future.rb +157 -82
  29. data/lib/fear/future_api.rb +17 -4
  30. data/lib/fear/left.rb +3 -9
  31. data/lib/fear/left_pattern_match.rb +2 -0
  32. data/lib/fear/none.rb +28 -10
  33. data/lib/fear/none_pattern_match.rb +2 -0
  34. data/lib/fear/option.rb +30 -2
  35. data/lib/fear/option_api.rb +4 -0
  36. data/lib/fear/option_pattern_match.rb +8 -3
  37. data/lib/fear/partial_function/and_then.rb +4 -2
  38. data/lib/fear/partial_function/any.rb +2 -0
  39. data/lib/fear/partial_function/combined.rb +3 -1
  40. data/lib/fear/partial_function/empty.rb +6 -0
  41. data/lib/fear/partial_function/guard/and.rb +2 -0
  42. data/lib/fear/partial_function/guard/and3.rb +2 -0
  43. data/lib/fear/partial_function/guard/or.rb +2 -0
  44. data/lib/fear/partial_function/guard.rb +8 -6
  45. data/lib/fear/partial_function/lifted.rb +2 -0
  46. data/lib/fear/partial_function/or_else.rb +5 -1
  47. data/lib/fear/partial_function.rb +18 -9
  48. data/lib/fear/partial_function_class.rb +3 -1
  49. data/lib/fear/pattern_match.rb +3 -11
  50. data/lib/fear/pattern_matching_api.rb +6 -28
  51. data/lib/fear/promise.rb +7 -5
  52. data/lib/fear/right.rb +3 -9
  53. data/lib/fear/right_biased.rb +5 -3
  54. data/lib/fear/right_pattern_match.rb +4 -0
  55. data/lib/fear/some.rb +35 -8
  56. data/lib/fear/some_pattern_match.rb +2 -0
  57. data/lib/fear/struct.rb +237 -0
  58. data/lib/fear/success.rb +7 -8
  59. data/lib/fear/success_pattern_match.rb +4 -0
  60. data/lib/fear/try.rb +8 -2
  61. data/lib/fear/try_api.rb +4 -0
  62. data/lib/fear/try_pattern_match.rb +9 -5
  63. data/lib/fear/unit.rb +6 -2
  64. data/lib/fear/utils.rb +14 -2
  65. data/lib/fear/version.rb +4 -1
  66. data/lib/fear.rb +26 -44
  67. data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
  68. data/spec/dry/types/fear/option/core_spec.rb +77 -0
  69. data/spec/dry/types/fear/option/default_spec.rb +21 -0
  70. data/spec/dry/types/fear/option/hash_spec.rb +58 -0
  71. data/spec/dry/types/fear/option/option_spec.rb +97 -0
  72. data/spec/fear/awaitable_spec.rb +19 -0
  73. data/spec/fear/done_spec.rb +7 -5
  74. data/spec/fear/either/mixin_spec.rb +4 -2
  75. data/spec/fear/either_pattern_match_spec.rb +10 -8
  76. data/spec/fear/either_pattern_matching_spec.rb +28 -0
  77. data/spec/fear/either_spec.rb +26 -0
  78. data/spec/fear/failure_spec.rb +57 -70
  79. data/spec/fear/for/mixin_spec.rb +15 -0
  80. data/spec/fear/for_spec.rb +19 -17
  81. data/spec/fear/future_spec.rb +477 -237
  82. data/spec/fear/guard_spec.rb +136 -24
  83. data/spec/fear/left_spec.rb +57 -70
  84. data/spec/fear/none_spec.rb +39 -43
  85. data/spec/fear/option/mixin_spec.rb +9 -7
  86. data/spec/fear/option_pattern_match_spec.rb +10 -8
  87. data/spec/fear/option_pattern_matching_spec.rb +34 -0
  88. data/spec/fear/option_spec.rb +142 -0
  89. data/spec/fear/partial_function/any_spec.rb +25 -0
  90. data/spec/fear/partial_function/empty_spec.rb +12 -10
  91. data/spec/fear/partial_function_and_then_spec.rb +39 -37
  92. data/spec/fear/partial_function_composition_spec.rb +46 -44
  93. data/spec/fear/partial_function_or_else_spec.rb +92 -90
  94. data/spec/fear/partial_function_spec.rb +91 -61
  95. data/spec/fear/pattern_match_spec.rb +19 -51
  96. data/spec/fear/pattern_matching_api_spec.rb +31 -0
  97. data/spec/fear/promise_spec.rb +23 -23
  98. data/spec/fear/right_biased/left.rb +28 -26
  99. data/spec/fear/right_biased/right.rb +51 -49
  100. data/spec/fear/right_spec.rb +48 -68
  101. data/spec/fear/some_spec.rb +30 -40
  102. data/spec/fear/success_spec.rb +40 -60
  103. data/spec/fear/try/mixin_spec.rb +19 -3
  104. data/spec/fear/try_api_spec.rb +23 -0
  105. data/spec/fear/try_pattern_match_spec.rb +10 -8
  106. data/spec/fear/try_pattern_matching_spec.rb +34 -0
  107. data/spec/fear/utils_spec.rb +16 -14
  108. data/spec/spec_helper.rb +13 -7
  109. data/spec/struct_pattern_matching_spec.rb +36 -0
  110. data/spec/struct_spec.rb +194 -0
  111. data/spec/support/dry_types.rb +6 -0
  112. metadata +128 -87
  113. data/.travis.yml +0 -13
  114. data/lib/fear/extractor/anonymous_array_splat_matcher.rb +0 -8
  115. data/lib/fear/extractor/any_matcher.rb +0 -15
  116. data/lib/fear/extractor/array_head_matcher.rb +0 -34
  117. data/lib/fear/extractor/array_matcher.rb +0 -38
  118. data/lib/fear/extractor/array_splat_matcher.rb +0 -14
  119. data/lib/fear/extractor/empty_list_matcher.rb +0 -18
  120. data/lib/fear/extractor/extractor_matcher.rb +0 -42
  121. data/lib/fear/extractor/grammar.rb +0 -201
  122. data/lib/fear/extractor/grammar.treetop +0 -129
  123. data/lib/fear/extractor/identifier_matcher.rb +0 -16
  124. data/lib/fear/extractor/matcher/and.rb +0 -36
  125. data/lib/fear/extractor/matcher.rb +0 -54
  126. data/lib/fear/extractor/named_array_splat_matcher.rb +0 -15
  127. data/lib/fear/extractor/pattern.rb +0 -55
  128. data/lib/fear/extractor/typed_identifier_matcher.rb +0 -24
  129. data/lib/fear/extractor/value_matcher.rb +0 -17
  130. data/lib/fear/extractor.rb +0 -108
  131. data/lib/fear/extractor_api.rb +0 -33
  132. data/spec/fear/extractor/array_matcher_spec.rb +0 -228
  133. data/spec/fear/extractor/extractor_matcher_spec.rb +0 -151
  134. data/spec/fear/extractor/grammar_array_spec.rb +0 -23
  135. data/spec/fear/extractor/identified_matcher_spec.rb +0 -47
  136. data/spec/fear/extractor/identifier_matcher_spec.rb +0 -66
  137. data/spec/fear/extractor/pattern_spec.rb +0 -32
  138. data/spec/fear/extractor/typed_identifier_matcher_spec.rb +0 -62
  139. data/spec/fear/extractor/value_matcher_number_spec.rb +0 -77
  140. data/spec/fear/extractor/value_matcher_string_spec.rb +0 -86
  141. data/spec/fear/extractor/value_matcher_symbol_spec.rb +0 -69
  142. data/spec/fear/extractor_api_spec.rb +0 -113
  143. data/spec/fear/extractor_spec.rb +0 -59
data/lib/fear/future.rb CHANGED
@@ -1,20 +1,14 @@
1
- begin
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
- # @example
13
- # success = "Hello"
14
- # f = Fear.future { success + ' future!' }
15
- # f.on_success do |result|
16
- # puts result
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
- # @example
27
- # f = Fear.future { 5 }
28
- # g = Fear.future { 3 }
29
- # f.flat_map do |x|
30
- # g.map { |y| x + y }
31
- # end
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
- # @example the same program may be written using +Fear.for+
34
- # Fear.for(Fear.future { 5 }, Fear.future { 3 }) do |x, y|
35
- # x + y
36
- # end
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::Future+ into +Fear::Future+.
49
+ # +Concurrent::Promise+ into +Fear::Future+.
43
50
  # @param options [see Concurrent::Future] options will be passed
44
- # directly to +Concurrent::Future+
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, 'pass block or future'
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 When this future is completed successfully.
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(@options)
180
- on_complete do |try|
181
- try.match do |m|
182
- m.success { |value| promise.success(success.call(value)) }
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(@options)
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 # rubocop: disable Metrics/MethodLength
226
- promise = Promise.new(@options)
227
- on_complete do |result|
228
- result.match do |m|
229
- m.failure { promise.complete!(result) }
230
- m.success do |value|
231
- begin
232
- yield(value).on_complete { |callback_result| promise.complete!(callback_result) }
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, '#select predicate is not satisfied'
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(@options)
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) # rubocop: disable Metrics/MethodLength
310
- promise = Promise.new(@options)
311
- on_complete do |try_of_self|
312
- try_of_self.match do |m|
313
- m.success do |value|
314
- other.on_complete do |try_of_other|
315
- promise.complete!(try_of_other.map { |other_value| [value, other_value] })
316
- end
317
- end
318
- m.failure do |error|
319
- promise.failure!(error)
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(@options)
344
- on_complete do |try|
345
- try.match do |m|
346
- m.success { |value| promise.complete!(value) }
347
- m.failure do |error|
348
- fallback.on_complete do |fallback_try|
349
- fallback_try.match do |m2|
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(&block)
383
- promise = Promise.new(@options)
443
+ def and_then
444
+ promise = Promise.new(**@options)
384
445
  on_complete do |try|
385
- Fear.try { try.match(&block) }
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(executor: Concurrent::ImmediateExecutor.new) do
399
- raise exception
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(executor: Concurrent::ImmediateExecutor.new) do
409
- result
410
- end
484
+ new { result }
485
+ .yield_self { |future| Fear::Await.ready(future, 10) }
411
486
  end
412
487
  end
413
488
  end
@@ -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 Metrics/LineLength
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 = {}, &block)
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 Metrics/LineLength
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.call(value)
49
+ reduce_left.(value)
56
50
  end
57
51
 
58
52
  # @return [self]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Fear
2
4
  # @api private
3
5
  class LeftPatternMatch < Fear::EitherPatternMatch
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
- '#<Fear::NoneClass>'
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 'you are not allowed to inherit from NoneClass, use Fear::None instead'
91
+ raise "you are not allowed to inherit from NoneClass, use Fear::None instead"
74
92
  end
75
93
  end
76
94
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Fear
2
4
  # @api private
3
5
  class NonePatternMatch < OptionPatternMatch
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(21)
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).call(value)
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"
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fear/option"
4
+
1
5
  module Fear
2
6
  module OptionApi
3
7
  # An +Option+ factory which creates +Some+ if the argument is
@@ -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, &GET_METHOD).and_then(Fear.case(*conditions, &effect))
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.call(partial_function.call(arg))
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.call(result)
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
  # Any is an object which is always truthy
@@ -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.call(f1.call(arg))
27
+ f2.(f1.(arg))
26
28
  end
27
29
 
28
30
  alias === call