fear 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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