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
@@ -0,0 +1,171 @@
|
|
1
|
+
module Fear
|
2
|
+
# A partial function is a unary function defined on subset of all possible inputs.
|
3
|
+
# The method +defined_at?+ allows to test dynamically if an arg is in
|
4
|
+
# the domain of the function.
|
5
|
+
#
|
6
|
+
# Even if +defined_at?+ returns true for given arg, calling +call+ may
|
7
|
+
# still throw an exception, so the following code is legal:
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# Fear.case(->(_) { true }) { 1/0 }
|
11
|
+
#
|
12
|
+
# It is the responsibility of the caller to call +defined_at?+ before
|
13
|
+
# calling +call+, because if +defined_at?+ is false, it is not guaranteed
|
14
|
+
# +call+ will throw an exception to indicate an error guard. If an
|
15
|
+
# exception is not thrown, evaluation may result in an arbitrary arg.
|
16
|
+
#
|
17
|
+
# The main distinction between +PartialFunction+ and +Proc+ is
|
18
|
+
# that the user of a +PartialFunction+ may choose to do something different
|
19
|
+
# with input that is declared to be outside its domain. For example:
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# sample = 1...10
|
23
|
+
#
|
24
|
+
# is_even = Fear.case(->(arg) { arg % 2 == 0}) do |arg|
|
25
|
+
# "#{arg} is even"
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# is_odd = Fear.case(->(arg) { arg % 2 == 1}) do |arg|
|
29
|
+
# "#{arg} is odd"
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# The method or_else allows chaining another partial function to handle
|
33
|
+
# input outside the declared domain
|
34
|
+
#
|
35
|
+
# numbers = sample.map(is_even.or_else(is_odd).to_proc)
|
36
|
+
#
|
37
|
+
# @see https://github.com/scala/scala/commit/5050915eb620af3aa43d6ddaae6bbb83ad74900d
|
38
|
+
module PartialFunction
|
39
|
+
autoload :AndThen, 'fear/partial_function/and_then'
|
40
|
+
autoload :Any, 'fear/partial_function/any'
|
41
|
+
autoload :Combined, 'fear/partial_function/combined'
|
42
|
+
autoload :EMPTY, 'fear/partial_function/empty'
|
43
|
+
autoload :Guard, 'fear/partial_function/guard'
|
44
|
+
autoload :Lifted, 'fear/partial_function/lifted'
|
45
|
+
autoload :OrElse, 'fear/partial_function/or_else'
|
46
|
+
|
47
|
+
# @param condition [#call] describes the domain of partial function
|
48
|
+
# @param function [Proc] function definition
|
49
|
+
def initialize(condition, &function)
|
50
|
+
@condition = condition
|
51
|
+
@function = function
|
52
|
+
end
|
53
|
+
attr_reader :condition, :function
|
54
|
+
private :condition
|
55
|
+
private :function
|
56
|
+
|
57
|
+
# Checks if a value is contained in the function's domain.
|
58
|
+
#
|
59
|
+
# @param arg [any]
|
60
|
+
# @return [Boolean]
|
61
|
+
def defined_at?(arg)
|
62
|
+
condition === arg
|
63
|
+
end
|
64
|
+
|
65
|
+
# @!method call(arg)
|
66
|
+
# @param arg [any]
|
67
|
+
# @return [any] Calls this partial function with the given argument when it
|
68
|
+
# is contained in the function domain.
|
69
|
+
# @raise [MatchError] when this partial function is not defined.
|
70
|
+
# @abstract
|
71
|
+
|
72
|
+
# Converts this partial function to other
|
73
|
+
#
|
74
|
+
# @return [Proc]
|
75
|
+
def to_proc
|
76
|
+
proc { |arg| call(arg) }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Calls this partial function with the given argument when it is contained in the function domain.
|
80
|
+
# Calls fallback function where this partial function is not defined.
|
81
|
+
#
|
82
|
+
# @param arg [any]
|
83
|
+
# @yield [arg] if partial function not defined for this +arg+
|
84
|
+
#
|
85
|
+
# @note that expression +pf.call_or_else(arg, &fallback)+ is equivalent to
|
86
|
+
# +pf.defined_at?(arg) ? pf.(arg) : fallback.(arg)+
|
87
|
+
# except that +call_or_else+ method can be implemented more efficiently to avoid calling +defined_at?+ twice.
|
88
|
+
#
|
89
|
+
def call_or_else(arg)
|
90
|
+
if defined_at?(arg)
|
91
|
+
call(arg)
|
92
|
+
else
|
93
|
+
yield arg
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Composes this partial function with a fallback partial function which
|
98
|
+
# gets applied where this partial function is not defined.
|
99
|
+
#
|
100
|
+
# @param other [PartialFunction]
|
101
|
+
# @return [PartialFunction] a partial function which has as domain the union of the domains
|
102
|
+
# of this partial function and +other+.
|
103
|
+
def or_else(other)
|
104
|
+
OrElse.new(self, other)
|
105
|
+
end
|
106
|
+
|
107
|
+
# @see or_else
|
108
|
+
def |(other)
|
109
|
+
or_else(other)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Composes this partial function with a fallback partial function (or Proc) which
|
113
|
+
# gets applied where this partial function is not defined.
|
114
|
+
#
|
115
|
+
# @overload and_then(other)
|
116
|
+
# @param other [Fear::PartialFunction]
|
117
|
+
# @return [Fear::PartialFunction] a partial function with the same domain as this partial function, which maps
|
118
|
+
# argument +x+ to +other.(self.call(x))+.
|
119
|
+
# @note calling +#defined_at?+ on the resulting partial function may call the first
|
120
|
+
# partial function and execute its side effect. It is highly recommended to call +#call_or_else+
|
121
|
+
# instead of +#defined_at?+/+#call+ for efficiency.
|
122
|
+
# @overload and_then(other)
|
123
|
+
# @param other [Proc]
|
124
|
+
# @return [Fear::PartialFunction] a partial function with the same domain as this partial function, which maps
|
125
|
+
# argument +x+ to +other.(self.call(x))+.
|
126
|
+
# @overload and_then(&other)
|
127
|
+
# @param other [Proc]
|
128
|
+
# @return [Fear::PartialFunction]
|
129
|
+
#
|
130
|
+
def and_then(other = Utils::UNDEFINED, &block)
|
131
|
+
Utils.with_block_or_argument('Fear::PartialFunction#and_then', other, block) do |fun|
|
132
|
+
if fun.is_a?(Fear::PartialFunction)
|
133
|
+
Combined.new(self, fun)
|
134
|
+
else
|
135
|
+
AndThen.new(self, &fun)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# @see and_then
|
141
|
+
def &(other)
|
142
|
+
and_then(other)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Turns this partial function in Proc-like object, returning +Option+
|
146
|
+
# @return [#call]
|
147
|
+
def lift
|
148
|
+
Lifted.new(self)
|
149
|
+
end
|
150
|
+
|
151
|
+
class << self
|
152
|
+
# Creates partial function guarded by several condition.
|
153
|
+
# All guards should match.
|
154
|
+
# @param guards [<#===, symbol>]
|
155
|
+
# @param function [Proc]
|
156
|
+
# @return [Fear::PartialFunction]
|
157
|
+
def and(*guards, &function)
|
158
|
+
PartialFunctionClass.new(Guard.and(guards), &function)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Creates partial function guarded by several condition.
|
162
|
+
# Any condition should match.
|
163
|
+
# @param guards [<#===, symbol>]
|
164
|
+
# @param function [Proc]
|
165
|
+
# @return [Fear::PartialFunction]
|
166
|
+
def or(*guards, &function)
|
167
|
+
PartialFunctionClass.new(Guard.or(guards), &function)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Fear
|
2
|
+
# @api private
|
3
|
+
class PartialFunctionClass
|
4
|
+
include PartialFunction
|
5
|
+
|
6
|
+
# @param arg [any]
|
7
|
+
# @return [any] Calls this partial function with the given argument when it
|
8
|
+
# is contained in the function domain.
|
9
|
+
# @raise [MatchError] when this partial function is not defined.
|
10
|
+
def call(arg)
|
11
|
+
call_or_else(arg, &PartialFunction::EMPTY)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param arg [any]
|
15
|
+
# @yield [arg] if function not defined
|
16
|
+
def call_or_else(arg)
|
17
|
+
if defined_at?(arg)
|
18
|
+
function.call(arg)
|
19
|
+
else
|
20
|
+
yield arg
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private_constant :PartialFunctionClass
|
26
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Fear
|
2
|
+
# Pattern match builder. Provides DSL for building pattern matcher
|
3
|
+
# Pattern match is just a combination of partial functions
|
4
|
+
#
|
5
|
+
# matcher = Fear.matcher do |m|
|
6
|
+
# m.case(Integer) { |x| x * 2 }
|
7
|
+
# m.case(String) { |x| x.to_i(10) * 2 }
|
8
|
+
# end
|
9
|
+
# matcher.is_a?(Fear::PartialFunction) #=> true
|
10
|
+
# matcher.defined_at?(4) #=> true
|
11
|
+
# matcher.defined_at?('4') #=> true
|
12
|
+
# matcher.defined_at?(nil) #=> false
|
13
|
+
#
|
14
|
+
# The previous example is the same as:
|
15
|
+
#
|
16
|
+
# Fear.case(Integer) { |x| x * ) }
|
17
|
+
# .or_else(
|
18
|
+
# Fear.case(String) { |x| x.to_i(10) * 2 }
|
19
|
+
# )
|
20
|
+
#
|
21
|
+
# You can provide +else+ branch, so partial function will be defined
|
22
|
+
# on any input:
|
23
|
+
#
|
24
|
+
# matcher = Fear.matcher do |m|
|
25
|
+
# m.else { 'Match' }
|
26
|
+
# end
|
27
|
+
# matcher.call(42) #=> 'Match'
|
28
|
+
#
|
29
|
+
# @see Fear.matcher public interface for building matchers
|
30
|
+
# @api Fear
|
31
|
+
# @note Use this class only to build custom pattern match classes. See +Fear::OptionPatternMatch+ as an example.
|
32
|
+
class PatternMatch
|
33
|
+
class << self
|
34
|
+
alias __new__ new
|
35
|
+
|
36
|
+
# @return [Fear::PartialFunction]
|
37
|
+
def new
|
38
|
+
builder = __new__(PartialFunction::EMPTY)
|
39
|
+
yield builder
|
40
|
+
builder.result
|
41
|
+
end
|
42
|
+
|
43
|
+
# Creates anonymous module to add `#mathing` behaviour to a class
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# class User
|
47
|
+
# include Fear::PatternMatch.mixin
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# user.match do |m\
|
51
|
+
# m.case(:admin?) { |u| ... }
|
52
|
+
# m.else { |u| ... }
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# @param as [Symbol, String] (:match) method name
|
56
|
+
# @return [Module]
|
57
|
+
def mixin(as: :match)
|
58
|
+
matcher_class = self
|
59
|
+
|
60
|
+
Module.new do
|
61
|
+
define_method(as) do |&matchers|
|
62
|
+
matcher_class.new(&matchers).call(self)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param result [Fear::EmptyPartialFunction]
|
69
|
+
def initialize(result)
|
70
|
+
@result = result
|
71
|
+
end
|
72
|
+
attr_accessor :result
|
73
|
+
private :result=
|
74
|
+
|
75
|
+
# @see Fear::PartialFunction#else
|
76
|
+
def else(&effect)
|
77
|
+
or_else(Fear.case(&effect))
|
78
|
+
end
|
79
|
+
|
80
|
+
# This method is syntactic sugar for `PartialFunction#or_else`, but rather than passing
|
81
|
+
# another partial function as an argument, you pass arguments to build such partial function.
|
82
|
+
# @example This two examples produces the same result
|
83
|
+
# other = Fear.case(Integer) { |x| x * 2 }
|
84
|
+
# this.or_else(other)
|
85
|
+
#
|
86
|
+
# this.case(Integer) { |x| x * 2 }
|
87
|
+
#
|
88
|
+
# @param guards [<#===>]
|
89
|
+
# @param effect [Proc]
|
90
|
+
# @return [Fear::PartialFunction]
|
91
|
+
# @see #or_else for details
|
92
|
+
def case(*guards, &effect)
|
93
|
+
or_else(Fear.case(*guards, &effect))
|
94
|
+
end
|
95
|
+
|
96
|
+
# @see Fear::PartialFunction#or_else
|
97
|
+
def or_else(other)
|
98
|
+
self.result = result.or_else(other)
|
99
|
+
self
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Fear
|
2
|
+
# @api private
|
3
|
+
module PatternMatchingApi
|
4
|
+
# Creates pattern match. Use `case` method to
|
5
|
+
# define matching branches. Branch consist of a
|
6
|
+
# guardian, which describes domain of the
|
7
|
+
# branch and function to apply to matching value.
|
8
|
+
#
|
9
|
+
# @example This mather apply different functions to Integers and to Strings
|
10
|
+
# matcher = Fear.matcher do |m|
|
11
|
+
# m.case(Integer) { |n| "#{n} is a number" }
|
12
|
+
# m.case(String) { |n| "#{n} is a string" }
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# matcher.(42) #=> "42 is a number"
|
16
|
+
# matcher.("Foo") #=> "Foo is a string"
|
17
|
+
#
|
18
|
+
# if you pass something other than Integer or string, it will raise `Fear::MatchError`:
|
19
|
+
#
|
20
|
+
# matcher.(10..20) #=> raises Fear::MatchError
|
21
|
+
#
|
22
|
+
# to avoid raising `MatchError`, you can use `else` method. It defines a branch matching
|
23
|
+
# on any value.
|
24
|
+
#
|
25
|
+
# matcher = Fear.matcher do |m|
|
26
|
+
# m.case(Integer) { |n| "#{n} is a number" }
|
27
|
+
# m.case(String) { |n| "#{n} is a string" }
|
28
|
+
# m.else { |n| "#{n} is a #{n.class}" }
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# matcher.(10..20) #=> "10..20 is a Range"
|
32
|
+
#
|
33
|
+
# You can use anything as a guardian if it responds to `#===` method:
|
34
|
+
#
|
35
|
+
# m.case(20..40) { |m| "#{m} is within range" }
|
36
|
+
# m.case(->(x) { x > 10}) { |m| "#{m} is greater than 10" }
|
37
|
+
#
|
38
|
+
# If you pass a Symbol, it will be converted to proc using +#to_proc+ method
|
39
|
+
#
|
40
|
+
# m.case(:even?) { |x| "#{x} is even" }
|
41
|
+
# m.case(:odd?) { |x| "#{x} is odd" }
|
42
|
+
#
|
43
|
+
# It's also possible to pass several guardians. All should match to pass
|
44
|
+
#
|
45
|
+
# m.case(Integer, :even?) { |x| ... }
|
46
|
+
# m.case(Integer, :odd?) { |x| ... }
|
47
|
+
#
|
48
|
+
# Since matcher returns +Fear::PartialFunction+, you can combine matchers using
|
49
|
+
# partial function API:
|
50
|
+
#
|
51
|
+
# failures = Fear.matcher do |m|
|
52
|
+
# m.case('not_found') { ... }
|
53
|
+
# m.case('network_error') { ... }
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# success = Fear.matcher do |m|
|
57
|
+
# m.case('ok') { ... }
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# response = failures.or_else(success)
|
61
|
+
#
|
62
|
+
# @yieldparam matcher [Fear::PartialFunction]
|
63
|
+
# @return [Fear::PartialFunction]
|
64
|
+
# @see Fear::OptionPatternMatch for example of custom matcher
|
65
|
+
def matcher(&block)
|
66
|
+
PatternMatch.new(&block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Pattern match against given value
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# Fear.match(42) do |m|
|
73
|
+
# m.case(Integer, :even?) { |n| "#{n} is even number" }
|
74
|
+
# m.case(Integer, :odd?) { |n| "#{n} is odd number" }
|
75
|
+
# m.case(Strings) { |n| "#{n} is a string" }
|
76
|
+
# m.else { 'unknown' }
|
77
|
+
# end #=> "42 is even number"
|
78
|
+
#
|
79
|
+
# @param value [any]
|
80
|
+
# @yieldparam matcher [Fear::PartialFunction]
|
81
|
+
# @return [any]
|
82
|
+
def match(value, &block)
|
83
|
+
matcher(&block).call(value)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Creates partial function defined on domain described with guards
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# pf = Fear.case(Integer) { |x| x / 2 }
|
90
|
+
# pf.defined_at?(4) #=> true
|
91
|
+
# pf.defined_at?('Foo') #=> false
|
92
|
+
#
|
93
|
+
# @example multiple guards combined using logical "and"
|
94
|
+
# pf = Fear.case(Integer, :even?) { |x| x / 2 }
|
95
|
+
# pf.defined_at?(4) #=> true
|
96
|
+
# pf.defined_at?(3) #=> false
|
97
|
+
#
|
98
|
+
# @note to make more complex matches, you are encouraged to use Qo gem.
|
99
|
+
# @see Qo https://github.com/baweaver/qo
|
100
|
+
# @example
|
101
|
+
# Fear.case(Qo[age: 20..30]) { |_| 'old enough' }
|
102
|
+
#
|
103
|
+
# @param guards [<#===, symbol>]
|
104
|
+
# @param function [Proc]
|
105
|
+
# @return [Fear::PartialFunction]
|
106
|
+
def case(*guards, &function)
|
107
|
+
PartialFunction.and(*guards, &function)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/fear/right.rb
CHANGED
@@ -2,6 +2,12 @@ module Fear
|
|
2
2
|
class Right
|
3
3
|
include Either
|
4
4
|
include RightBiased::Right
|
5
|
+
include RightPatternMatch.mixin
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
def right_value
|
9
|
+
value
|
10
|
+
end
|
5
11
|
|
6
12
|
# @return [true]
|
7
13
|
def right?
|
@@ -50,7 +56,7 @@ module Fear
|
|
50
56
|
|
51
57
|
# @param reduce_right [Proc]
|
52
58
|
# @return [any]
|
53
|
-
def reduce(
|
59
|
+
def reduce(_reduce_left, reduce_right)
|
54
60
|
reduce_right.call(value)
|
55
61
|
end
|
56
62
|
|
data/lib/fear/right_biased.rb
CHANGED
@@ -85,11 +85,6 @@ module Fear
|
|
85
85
|
yield(value)
|
86
86
|
end
|
87
87
|
|
88
|
-
# @return [Array] containing value
|
89
|
-
def to_a
|
90
|
-
[value]
|
91
|
-
end
|
92
|
-
|
93
88
|
# @return [Option] containing value
|
94
89
|
def to_option
|
95
90
|
Some.new(value)
|
@@ -136,7 +131,7 @@ module Fear
|
|
136
131
|
# @param [any]
|
137
132
|
# @return [false]
|
138
133
|
#
|
139
|
-
def include?(
|
134
|
+
def include?(_value)
|
140
135
|
false
|
141
136
|
end
|
142
137
|
|
@@ -164,14 +159,9 @@ module Fear
|
|
164
159
|
self
|
165
160
|
end
|
166
161
|
|
167
|
-
# @return [Array] empty array
|
168
|
-
def to_a
|
169
|
-
[]
|
170
|
-
end
|
171
|
-
|
172
162
|
# @return [None]
|
173
163
|
def to_option
|
174
|
-
None
|
164
|
+
None
|
175
165
|
end
|
176
166
|
|
177
167
|
# @return [false]
|
data/lib/fear/some.rb
CHANGED
@@ -3,10 +3,13 @@ module Fear
|
|
3
3
|
include Option
|
4
4
|
include Dry::Equalizer(:get)
|
5
5
|
include RightBiased::Right
|
6
|
+
include SomePatternMatch.mixin
|
6
7
|
|
7
8
|
attr_reader :value
|
8
9
|
protected :value
|
9
10
|
|
11
|
+
# FIXME: nice inspect
|
12
|
+
|
10
13
|
def initialize(value)
|
11
14
|
@value = value
|
12
15
|
end
|
@@ -31,14 +34,14 @@ module Fear
|
|
31
34
|
if yield(value)
|
32
35
|
self
|
33
36
|
else
|
34
|
-
None
|
37
|
+
None
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
38
41
|
# @return [Option]
|
39
42
|
def reject
|
40
43
|
if yield(value)
|
41
|
-
None
|
44
|
+
None
|
42
45
|
else
|
43
46
|
self
|
44
47
|
end
|
data/lib/fear/success.rb
CHANGED
@@ -3,6 +3,7 @@ module Fear
|
|
3
3
|
include Try
|
4
4
|
include Dry::Equalizer(:value)
|
5
5
|
include RightBiased::Right
|
6
|
+
include SuccessPatternMatch.mixin
|
6
7
|
|
7
8
|
attr_reader :value
|
8
9
|
protected :value
|
@@ -48,9 +49,9 @@ module Fear
|
|
48
49
|
if yield(value)
|
49
50
|
self
|
50
51
|
else
|
51
|
-
|
52
|
+
raise NoSuchElementError, "Predicate does not hold for `#{value}`"
|
52
53
|
end
|
53
|
-
rescue => error
|
54
|
+
rescue StandardError => error
|
54
55
|
Failure.new(error)
|
55
56
|
end
|
56
57
|
|
@@ -67,14 +68,14 @@ module Fear
|
|
67
68
|
# @return [Try]
|
68
69
|
def map
|
69
70
|
super
|
70
|
-
rescue => error
|
71
|
+
rescue StandardError => error
|
71
72
|
Failure.new(error)
|
72
73
|
end
|
73
74
|
|
74
75
|
# @return [Try]
|
75
76
|
def flat_map
|
76
77
|
super
|
77
|
-
rescue => error
|
78
|
+
rescue StandardError => error
|
78
79
|
Failure.new(error)
|
79
80
|
end
|
80
81
|
|
data/lib/fear/try.rb
CHANGED
@@ -15,11 +15,19 @@ module Fear
|
|
15
15
|
# divisor = Try { Integer(params[:divisor]) }
|
16
16
|
# problem = dividend.flat_map { |x| divisor.map { |y| x / y } }
|
17
17
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
18
|
+
# problem.match |m|
|
19
|
+
# m.success do |result|
|
20
|
+
# puts "Result of #{dividend.get} / #{divisor.get} is: #{result}"
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# m.failure(ZeroDivisionError) do
|
24
|
+
# puts "Division by zero is not allowed"
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# m.failure do |exception|
|
28
|
+
# puts "You entered something wrong. Try again"
|
29
|
+
# puts "Info from the exception: #{exception.message}"
|
30
|
+
# end
|
23
31
|
# end
|
24
32
|
#
|
25
33
|
# An important property of +Try+ shown in the above example is its
|
@@ -99,14 +107,6 @@ module Fear
|
|
99
107
|
# Failure(ArgumentError.new).flat_map { |v| Success(v/2) }
|
100
108
|
# #=> Failure(ArgumentError.new)
|
101
109
|
#
|
102
|
-
# @!method to_a
|
103
|
-
# Returns an +Array+ containing the +Success+ value or an
|
104
|
-
# empty +Array+ if this is a +Failure+.
|
105
|
-
# @return [Array]
|
106
|
-
# @example
|
107
|
-
# Success(42).to_a #=> [21]
|
108
|
-
# Failure(ArgumentError.new).to_a #=> []
|
109
|
-
#
|
110
110
|
# @!method to_option
|
111
111
|
# Returns an +Some+ containing the +Success+ value or a +None+ if
|
112
112
|
# this is a +Failure+.
|
@@ -186,7 +186,7 @@ module Fear
|
|
186
186
|
# #=> Success(42)
|
187
187
|
# Failure(ArgumentError.new).recover_with { |e| Success(e.massage) }
|
188
188
|
# #=> Success('ArgumentError')
|
189
|
-
# Failure(ArgumentError.new).recover_with { |e|
|
189
|
+
# Failure(ArgumentError.new).recover_with { |e| raise }
|
190
190
|
# #=> Failure(RuntimeError)
|
191
191
|
#
|
192
192
|
# @!method recover(&block)
|
@@ -199,7 +199,7 @@ module Fear
|
|
199
199
|
# #=> Success(42)
|
200
200
|
# Failure(ArgumentError.new).recover { |e| e.massage }
|
201
201
|
# #=> Success('ArgumentError')
|
202
|
-
# Failure(ArgumentError.new).recover { |e|
|
202
|
+
# Failure(ArgumentError.new).recover { |e| raise }
|
203
203
|
# #=> Failure(RuntimeError)
|
204
204
|
#
|
205
205
|
# @!method to_either
|
@@ -210,6 +210,23 @@ module Fear
|
|
210
210
|
# Success(42).to_either #=> Right(42)
|
211
211
|
# Failure(ArgumentError.new).to_either #=> Left(ArgumentError.new)
|
212
212
|
#
|
213
|
+
# @!method match(&matcher)
|
214
|
+
# Pattern match against this +Try+
|
215
|
+
# @yield matcher [Fear::TryPatternMatch]
|
216
|
+
# @example
|
217
|
+
# Try { ... }.match do |m|
|
218
|
+
# m.success(Integer) do |x|
|
219
|
+
# x * 2
|
220
|
+
# end
|
221
|
+
#
|
222
|
+
# m.success(String) do |x|
|
223
|
+
# x.to_i * 2
|
224
|
+
# end
|
225
|
+
#
|
226
|
+
# m.failure(ZeroDivisionError) { 'not allowed to divide by 0' }
|
227
|
+
# m.else { 'something unexpected' }
|
228
|
+
# end
|
229
|
+
#
|
213
230
|
# @author based on Twitter's original implementation.
|
214
231
|
# @see https://github.com/scala/scala/blob/2.11.x/src/library/scala/util/Try.scala
|
215
232
|
#
|
@@ -224,6 +241,29 @@ module Fear
|
|
224
241
|
Success
|
225
242
|
end
|
226
243
|
|
244
|
+
class << self
|
245
|
+
# Build pattern matcher to be used later, despite off
|
246
|
+
# +Try#match+ method, id doesn't apply matcher immanently,
|
247
|
+
# but build it instead. Unusually in sake of efficiency it's better
|
248
|
+
# to statically build matcher and reuse it later.
|
249
|
+
#
|
250
|
+
# @example
|
251
|
+
# matcher =
|
252
|
+
# Try.matcher do |m|
|
253
|
+
# m.success(Integer, ->(x) { x > 2 }) { |x| x * 2 }
|
254
|
+
# m.success(String) { |x| x.to_i * 2 }
|
255
|
+
# m.failure(ActiveRecord::RecordNotFound) { :err }
|
256
|
+
# m.else { 'error '}
|
257
|
+
# end
|
258
|
+
# matcher.call(try)
|
259
|
+
#
|
260
|
+
# @yieldparam [Fear::TryPatternMatch]
|
261
|
+
# @return [Fear::PartialFunction]
|
262
|
+
def matcher(&matcher)
|
263
|
+
TryPatternMatch.new(&matcher)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
227
267
|
# Include this mixin to access convenient factory methods.
|
228
268
|
# @example
|
229
269
|
# include Fear::Try::Mixin
|
@@ -240,7 +280,7 @@ module Fear
|
|
240
280
|
#
|
241
281
|
def Try
|
242
282
|
Success.new(yield)
|
243
|
-
rescue => error
|
283
|
+
rescue StandardError => error
|
244
284
|
Failure.new(error)
|
245
285
|
end
|
246
286
|
|