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
@@ -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
|
|