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