fear 0.10.0 → 0.11.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/.rubocop.yml +30 -4
- data/.travis.yml +2 -3
- data/Appraisals +5 -9
- data/CHANGELOG.md +9 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +255 -85
- data/Rakefile +393 -0
- data/fear.gemspec +13 -6
- data/gemfiles/dry_equalizer_0.1.0.gemfile +1 -0
- data/gemfiles/dry_equalizer_0.1.0.gemfile.lock +31 -27
- data/gemfiles/dry_equalizer_0.2.1.gemfile +1 -0
- data/gemfiles/dry_equalizer_0.2.1.gemfile.lock +31 -27
- data/lib/fear/either.rb +49 -14
- data/lib/fear/either_pattern_match.rb +48 -0
- data/lib/fear/empty_partial_function.rb +36 -0
- data/lib/fear/failure.rb +5 -4
- data/lib/fear/failure_pattern_match.rb +8 -0
- data/lib/fear/for.rb +46 -51
- data/lib/fear/left.rb +7 -1
- data/lib/fear/left_pattern_match.rb +9 -0
- data/lib/fear/none.rb +37 -2
- data/lib/fear/none_pattern_match.rb +12 -0
- data/lib/fear/option.rb +65 -31
- data/lib/fear/option_pattern_match.rb +45 -0
- data/lib/fear/partial_function/and_then.rb +48 -0
- data/lib/fear/partial_function/any.rb +26 -0
- data/lib/fear/partial_function/combined.rb +51 -0
- data/lib/fear/partial_function/empty.rb +6 -0
- data/lib/fear/partial_function/guard/and.rb +36 -0
- data/lib/fear/partial_function/guard/and3.rb +39 -0
- data/lib/fear/partial_function/guard/or.rb +36 -0
- data/lib/fear/partial_function/guard.rb +90 -0
- data/lib/fear/partial_function/lifted.rb +20 -0
- data/lib/fear/partial_function/or_else.rb +62 -0
- data/lib/fear/partial_function.rb +171 -0
- data/lib/fear/partial_function_class.rb +26 -0
- data/lib/fear/pattern_match.rb +102 -0
- data/lib/fear/pattern_matching_api.rb +110 -0
- data/lib/fear/right.rb +7 -1
- data/lib/fear/right_biased.rb +2 -12
- data/lib/fear/right_pattern_match.rb +9 -0
- data/lib/fear/some.rb +5 -2
- data/lib/fear/some_pattern_match.rb +11 -0
- data/lib/fear/success.rb +5 -4
- data/lib/fear/success_pattern_match.rb +10 -0
- data/lib/fear/try.rb +56 -16
- data/lib/fear/try_pattern_match.rb +28 -0
- data/lib/fear/utils.rb +24 -14
- data/lib/fear/version.rb +1 -1
- data/lib/fear.rb +21 -4
- data/spec/fear/either_pattern_match_spec.rb +37 -0
- data/spec/fear/failure_spec.rb +41 -3
- data/spec/fear/for_spec.rb +17 -29
- data/spec/fear/guard_spec.rb +101 -0
- data/spec/fear/left_spec.rb +38 -0
- data/spec/fear/none_spec.rb +80 -0
- data/spec/fear/option_pattern_match_spec.rb +35 -0
- data/spec/fear/partial_function/empty_spec.rb +36 -0
- data/spec/fear/partial_function_and_then_spec.rb +145 -0
- data/spec/fear/partial_function_composition_spec.rb +80 -0
- data/spec/fear/partial_function_or_else_spec.rb +274 -0
- data/spec/fear/partial_function_spec.rb +165 -0
- data/spec/fear/pattern_match_spec.rb +59 -0
- data/spec/fear/right_biased/left.rb +1 -6
- data/spec/fear/right_biased/right.rb +0 -5
- data/spec/fear/right_spec.rb +38 -0
- data/spec/fear/some_spec.rb +37 -0
- data/spec/fear/success_spec.rb +41 -4
- data/spec/fear/try_pattern_match_spec.rb +37 -0
- metadata +97 -23
- data/lib/fear/for/evaluation_context.rb +0 -91
data/lib/fear/either.rb
CHANGED
@@ -19,12 +19,15 @@ module Fear
|
|
19
19
|
# Left(in)
|
20
20
|
# end
|
21
21
|
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
22
|
+
# result.match do |m|
|
23
|
+
# m.right do |x|
|
24
|
+
# "You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}"
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# m.left do |x|
|
28
|
+
# "You passed me the String: #{x}"
|
29
|
+
# end
|
30
|
+
# end
|
28
31
|
#
|
29
32
|
# Either is right-biased, which means that +Right+ is assumed to be the default case to
|
30
33
|
# operate on. If it is +Left+, operations like +#map+, +#flat_map+, ... return the +Left+ value
|
@@ -96,14 +99,6 @@ module Fear
|
|
96
99
|
# Right(42).flat_map { |v| Right(v/2) } #=> Right(21)
|
97
100
|
# Left('undefined').flat_map { |v| Right(v/2) } #=> Left('undefined')
|
98
101
|
#
|
99
|
-
# @!method to_a
|
100
|
-
# Returns an +Array+ containing the +Right+ value or an
|
101
|
-
# empty +Array+ if this is a +Left+.
|
102
|
-
# @return [Array]
|
103
|
-
# @example
|
104
|
-
# Right(42).to_a #=> [21]
|
105
|
-
# Left('undefined').to_a #=> []
|
106
|
-
#
|
107
102
|
# @!method to_option
|
108
103
|
# Returns an +Some+ containing the +Right+ value or a +None+ if
|
109
104
|
# this is a +Left+.
|
@@ -227,6 +222,23 @@ module Fear
|
|
227
222
|
# Right("daisy").join_left #=> Right("daisy")
|
228
223
|
# Right(Left("daisy")).join_left #=> Right(Left("daisy"))
|
229
224
|
#
|
225
|
+
# @!method match(&matcher)
|
226
|
+
# Pattern match against this +Either+
|
227
|
+
# @yield matcher [Fear::EitherPatternMatch]
|
228
|
+
# @example
|
229
|
+
# Either(val).match do |m|
|
230
|
+
# m.right(Integer) do |x|
|
231
|
+
# x * 2
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# m.right(String) do |x|
|
235
|
+
# x.to_i * 2
|
236
|
+
# end
|
237
|
+
#
|
238
|
+
# m.left { |x| x }
|
239
|
+
# m.else { 'something unexpected' }
|
240
|
+
# end
|
241
|
+
#
|
230
242
|
# @see https://github.com/scala/scala/blob/2.12.x/src/library/scala/util/Either.scala
|
231
243
|
#
|
232
244
|
module Either
|
@@ -249,6 +261,29 @@ module Fear
|
|
249
261
|
attr_reader :value
|
250
262
|
protected :value
|
251
263
|
|
264
|
+
class << self
|
265
|
+
# Build pattern matcher to be used later, despite off
|
266
|
+
# +Either#match+ method, id doesn't apply matcher immanently,
|
267
|
+
# but build it instead. Unusually in sake of efficiency it's better
|
268
|
+
# to statically build matcher and reuse it later.
|
269
|
+
#
|
270
|
+
# @example
|
271
|
+
# matcher =
|
272
|
+
# Either.matcher do |m|
|
273
|
+
# m.right(Integer, ->(x) { x > 2 }) { |x| x * 2 }
|
274
|
+
# m.right(String) { |x| x.to_i * 2 }
|
275
|
+
# m.left(String) { :err }
|
276
|
+
# m.else { 'error '}
|
277
|
+
# end
|
278
|
+
# matcher.call(Some(42))
|
279
|
+
#
|
280
|
+
# @yieldparam [Fear::EitherPatternMatch]
|
281
|
+
# @return [Fear::PartialFunction]
|
282
|
+
def matcher(&matcher)
|
283
|
+
EitherPatternMatch.new(&matcher)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
252
287
|
# Include this mixin to access convenient factory methods.
|
253
288
|
# @example
|
254
289
|
# include Fear::Either::Mixin
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Fear
|
2
|
+
# Either pattern matcher
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
# pattern_match =
|
6
|
+
# EitherPatternMatch.new
|
7
|
+
# .right(Integer, ->(x) { x > 2 }) { |x| x * 2 }
|
8
|
+
# .right(String) { |x| x.to_i * 2 }
|
9
|
+
# .left(String) { :err }
|
10
|
+
# .else { 'error '}
|
11
|
+
#
|
12
|
+
# pattern_match.call(42) => 'NaN'
|
13
|
+
#
|
14
|
+
# @example the same matcher may be defined using block syntax
|
15
|
+
# EitherPatternMatch.new do |m|
|
16
|
+
# m.right(Integer, ->(x) { x > 2 }) { |x| x * 2 }
|
17
|
+
# m.right(String) { |x| x.to_i * 2 }
|
18
|
+
# m.left(String) { :err }
|
19
|
+
# m.else { 'error '}
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @note it has two optimized subclasses +Fear::LeftPatternMatch+ and +Fear::RightPatternMatch+
|
23
|
+
# @api private
|
24
|
+
class EitherPatternMatch < Fear::PatternMatch
|
25
|
+
LEFT_EXTRACTOR = :left_value.to_proc
|
26
|
+
RIGHT_EXTRACTOR = :right_value.to_proc
|
27
|
+
|
28
|
+
# Match against +Fear::Right+
|
29
|
+
#
|
30
|
+
# @param conditions [<#==>]
|
31
|
+
# @return [Fear::EitherPatternMatch]
|
32
|
+
def right(*conditions, &effect)
|
33
|
+
branch = Fear.case(Fear::Right, &RIGHT_EXTRACTOR).and_then(Fear.case(*conditions, &effect))
|
34
|
+
or_else(branch)
|
35
|
+
end
|
36
|
+
alias success right
|
37
|
+
|
38
|
+
# Match against +Fear::Left+
|
39
|
+
#
|
40
|
+
# @param conditions [<#==>]
|
41
|
+
# @return [Fear::EitherPatternMatch]
|
42
|
+
def left(*conditions, &effect)
|
43
|
+
branch = Fear.case(Fear::Left, &LEFT_EXTRACTOR).and_then(Fear.case(*conditions, &effect))
|
44
|
+
or_else(branch)
|
45
|
+
end
|
46
|
+
alias failure left
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Fear
|
2
|
+
# Use singleton version of EmptyPartialFunction -- PartialFunction::EMPTY
|
3
|
+
# @api private
|
4
|
+
module EmptyPartialFunction
|
5
|
+
include PartialFunction
|
6
|
+
|
7
|
+
def defined_at?(_)
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(arg)
|
12
|
+
raise MatchError, "partial function not defined at: #{arg}"
|
13
|
+
end
|
14
|
+
|
15
|
+
alias === call
|
16
|
+
alias [] call
|
17
|
+
|
18
|
+
def call_or_else(arg)
|
19
|
+
yield arg
|
20
|
+
end
|
21
|
+
|
22
|
+
def or_else(other)
|
23
|
+
other
|
24
|
+
end
|
25
|
+
|
26
|
+
def and_then(*)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
'Empty partial function'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private_constant :EmptyPartialFunction
|
36
|
+
end
|
data/lib/fear/failure.rb
CHANGED
@@ -3,6 +3,7 @@ module Fear
|
|
3
3
|
include Try
|
4
4
|
include Dry::Equalizer(:exception)
|
5
5
|
include RightBiased::Left
|
6
|
+
include FailurePatternMatch.mixin
|
6
7
|
|
7
8
|
# @param [StandardError]
|
8
9
|
def initialize(exception)
|
@@ -23,13 +24,13 @@ module Fear
|
|
23
24
|
|
24
25
|
# @raise
|
25
26
|
def get
|
26
|
-
|
27
|
+
raise exception
|
27
28
|
end
|
28
29
|
|
29
30
|
# @return [Try] of calling block
|
30
31
|
def or_else(*args)
|
31
32
|
super
|
32
|
-
rescue => error
|
33
|
+
rescue StandardError => error
|
33
34
|
Failure.new(error)
|
34
35
|
end
|
35
36
|
|
@@ -50,7 +51,7 @@ module Fear
|
|
50
51
|
yield(exception).tap do |result|
|
51
52
|
Utils.assert_type!(result, Success, Failure)
|
52
53
|
end
|
53
|
-
rescue => error
|
54
|
+
rescue StandardError => error
|
54
55
|
Failure.new(error)
|
55
56
|
end
|
56
57
|
|
@@ -59,7 +60,7 @@ module Fear
|
|
59
60
|
# @return [Try]
|
60
61
|
def recover
|
61
62
|
Success.new(yield(exception))
|
62
|
-
rescue => error
|
63
|
+
rescue StandardError => error
|
63
64
|
Failure.new(error)
|
64
65
|
end
|
65
66
|
|
data/lib/fear/for.rb
CHANGED
@@ -4,18 +4,20 @@ module Fear
|
|
4
4
|
# operations - +flat_map+ and +map+. Any class providing them
|
5
5
|
# is supported by +For+.
|
6
6
|
#
|
7
|
-
# For(
|
7
|
+
# For(Some(2), Some(3)) do |a, b|
|
8
|
+
# a * b
|
9
|
+
# end #=> Some(6)
|
8
10
|
#
|
9
11
|
# If one of operands is None, the result is None
|
10
12
|
#
|
11
|
-
# For(
|
12
|
-
# For(
|
13
|
+
# For(Some(2), None()) { |a, b| a * b } #=> None()
|
14
|
+
# For(None(), Some(2)) { |a, b| a * b } #=> None()
|
13
15
|
#
|
14
16
|
# Lets look at first example:
|
15
17
|
#
|
16
|
-
# For(
|
18
|
+
# For(Some(2), Some(3)) { |a, b| a * b }
|
17
19
|
#
|
18
|
-
#
|
20
|
+
# it is translated to:
|
19
21
|
#
|
20
22
|
# Some(2).flat_map do |a|
|
21
23
|
# Some(3).map do |b|
|
@@ -25,10 +27,10 @@ module Fear
|
|
25
27
|
#
|
26
28
|
# It works with arrays as well
|
27
29
|
#
|
28
|
-
# For(
|
30
|
+
# For([1, 2], [2, 3], [3, 4]) { |a, b, c| a * b * c }
|
29
31
|
# #=> [6, 8, 9, 12, 12, 16, 18, 24]
|
30
32
|
#
|
31
|
-
#
|
33
|
+
# it is translated to:
|
32
34
|
#
|
33
35
|
# [1, 2].flat_map do |a|
|
34
36
|
# [2, 3].flat_map do |b|
|
@@ -38,59 +40,52 @@ module Fear
|
|
38
40
|
# end
|
39
41
|
# end
|
40
42
|
#
|
41
|
-
# If you pass lambda
|
43
|
+
# If you pass lambda instead of monad, it would be evaluated
|
42
44
|
# only on demand.
|
43
45
|
#
|
44
|
-
# For(
|
45
|
-
#
|
46
|
+
# For(proc { None() }, proc { raise 'kaboom' } ) do |a, b|
|
47
|
+
# a * b
|
48
|
+
# end #=> None()
|
46
49
|
#
|
47
50
|
# It does not fail since `b` is not evaluated.
|
48
|
-
# You can refer to previously defined
|
51
|
+
# You can refer to previously defined monads from within lambdas.
|
49
52
|
#
|
50
53
|
# maybe_user = find_user('Paul') #=> <#Option value=<#User ...>>
|
51
54
|
#
|
52
|
-
# For(
|
55
|
+
# For(maybe_user, ->(user) { user.birthday }) do |user, birthday|
|
53
56
|
# "#{user.name} was born on #{birthday}"
|
54
57
|
# end #=> Some('Paul was born on 1987-06-17')
|
55
58
|
#
|
56
|
-
|
57
|
-
|
59
|
+
module For
|
60
|
+
module_function # rubocop: disable Style/AccessModifierDeclarations
|
58
61
|
|
59
|
-
# @param
|
62
|
+
# @param monads [<Fear::Option, Fear::Either, Fear::Try, Proc>]
|
60
63
|
#
|
61
|
-
def
|
62
|
-
|
63
|
-
@evaluation_context = EvaluationContext.new(outer_context)
|
64
|
-
end
|
64
|
+
def call(monads, inner_values = [], &block)
|
65
|
+
head, *tail = *monads
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
67
|
+
if tail.length.zero?
|
68
|
+
map(head, inner_values, &block)
|
69
|
+
else
|
70
|
+
flat_map(head, tail, inner_values, &block)
|
71
|
+
end
|
69
72
|
end
|
70
73
|
|
71
|
-
private
|
72
|
-
|
73
|
-
|
74
|
-
|
74
|
+
private def map(head, inner_values)
|
75
|
+
resolve(head, inner_values).map do |x|
|
76
|
+
yield(*inner_values, x)
|
77
|
+
end
|
78
|
+
end
|
75
79
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
evaluation_context.__assign__(variable_name, value)
|
80
|
-
evaluation_context.instance_exec(&block)
|
81
|
-
end
|
82
|
-
else
|
83
|
-
resolve(monad).flat_map do |value|
|
84
|
-
evaluation_context.__assign__(variable_name, value)
|
85
|
-
variable_name_and_monad, *tail = *monads
|
86
|
-
execute(*variable_name_and_monad, tail, &block)
|
87
|
-
end
|
80
|
+
private def flat_map(head, tail, inner_values, &block)
|
81
|
+
resolve(head, inner_values).flat_map do |x|
|
82
|
+
call(tail, inner_values + [x], &block)
|
88
83
|
end
|
89
84
|
end
|
90
85
|
|
91
|
-
def resolve(monad_or_proc)
|
86
|
+
private def resolve(monad_or_proc, inner_values)
|
92
87
|
if monad_or_proc.respond_to?(:call)
|
93
|
-
|
88
|
+
monad_or_proc.call(*inner_values)
|
94
89
|
else
|
95
90
|
monad_or_proc
|
96
91
|
end
|
@@ -100,29 +95,29 @@ module Fear
|
|
100
95
|
# @example
|
101
96
|
# include Fear::For::Mixin
|
102
97
|
#
|
103
|
-
# For(
|
104
|
-
# For(
|
98
|
+
# For(Some(2), Some(3)) { |a, b| a * b } #=> Some(6)
|
99
|
+
# For(Some(2), None()) { |a, b| a * b } #=> None()
|
105
100
|
#
|
106
|
-
# For(
|
101
|
+
# For(proc { Some(2) }, proc { Some(3) }) do |a, b|
|
107
102
|
# a * b
|
108
103
|
# end #=> Some(6)
|
109
104
|
#
|
110
|
-
# For(
|
105
|
+
# For(proc { None() }, proc { raise }) do |a, b|
|
111
106
|
# a * b
|
112
107
|
# end #=> None()
|
113
108
|
#
|
114
|
-
# For(
|
115
|
-
# For(
|
109
|
+
# For(Right(2), Right(3)) { |a, b| a * b } #=> Right(6)
|
110
|
+
# For(Right(2), Left(3)) { |a, b| a * b } #=> Left(3)
|
116
111
|
#
|
117
|
-
# For(
|
118
|
-
# For(
|
112
|
+
# For(Success(2), Success(3)) { |a| a * b } #=> Success(3)
|
113
|
+
# For(Success(2), Failure(...)) { |a, b| a * b } #=> Failure(...)
|
119
114
|
#
|
120
115
|
module Mixin
|
121
|
-
# @param
|
116
|
+
# @param monads [Hash{Symbol => {#map, #flat_map}}]
|
122
117
|
# @return [{#map, #flat_map}]
|
123
118
|
#
|
124
|
-
def For(
|
125
|
-
For.
|
119
|
+
def For(*monads, &block)
|
120
|
+
For.call(monads, &block)
|
126
121
|
end
|
127
122
|
end
|
128
123
|
end
|
data/lib/fear/left.rb
CHANGED
@@ -2,6 +2,12 @@ module Fear
|
|
2
2
|
class Left
|
3
3
|
include Either
|
4
4
|
include RightBiased::Left
|
5
|
+
include LeftPatternMatch.mixin
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
def left_value
|
9
|
+
value
|
10
|
+
end
|
5
11
|
|
6
12
|
# @return [false]
|
7
13
|
def right?
|
@@ -37,7 +43,7 @@ module Fear
|
|
37
43
|
|
38
44
|
# @param reduce_left [Proc]
|
39
45
|
# @return [any]
|
40
|
-
def reduce(reduce_left,
|
46
|
+
def reduce(reduce_left, _reduce_right)
|
41
47
|
reduce_left.call(value)
|
42
48
|
end
|
43
49
|
|
data/lib/fear/none.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
module Fear
|
2
|
-
|
2
|
+
# @api private
|
3
|
+
class NoneClass
|
3
4
|
include Option
|
4
5
|
include Dry::Equalizer()
|
5
6
|
include RightBiased::Left
|
7
|
+
include NonePatternMatch.mixin
|
6
8
|
|
7
9
|
# @raise [NoSuchElementError]
|
8
10
|
def get
|
9
|
-
|
11
|
+
raise NoSuchElementError
|
10
12
|
end
|
11
13
|
|
12
14
|
# @return [nil]
|
@@ -28,5 +30,38 @@ module Fear
|
|
28
30
|
def reject(*)
|
29
31
|
self
|
30
32
|
end
|
33
|
+
|
34
|
+
AS_STRING = 'Fear::None'.freeze
|
35
|
+
|
36
|
+
# @return [String]
|
37
|
+
def to_s
|
38
|
+
AS_STRING
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String]
|
42
|
+
def inspect
|
43
|
+
AS_STRING
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param other
|
47
|
+
# @return [Boolean]
|
48
|
+
def ===(other)
|
49
|
+
self == other
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private_constant(:NoneClass)
|
54
|
+
|
55
|
+
# The only instance of NoneClass
|
56
|
+
None = NoneClass.new.freeze
|
57
|
+
|
58
|
+
class << NoneClass
|
59
|
+
def new
|
60
|
+
None
|
61
|
+
end
|
62
|
+
|
63
|
+
def inherited
|
64
|
+
raise 'you are not allowed to inherit from NoneClass, use Fear::None instead'
|
65
|
+
end
|
31
66
|
end
|
32
67
|
end
|