fear 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +30 -4
  3. data/.travis.yml +2 -3
  4. data/Appraisals +5 -9
  5. data/CHANGELOG.md +9 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE.txt +1 -1
  8. data/README.md +255 -85
  9. data/Rakefile +393 -0
  10. data/fear.gemspec +13 -6
  11. data/gemfiles/dry_equalizer_0.1.0.gemfile +1 -0
  12. data/gemfiles/dry_equalizer_0.1.0.gemfile.lock +31 -27
  13. data/gemfiles/dry_equalizer_0.2.1.gemfile +1 -0
  14. data/gemfiles/dry_equalizer_0.2.1.gemfile.lock +31 -27
  15. data/lib/fear/either.rb +49 -14
  16. data/lib/fear/either_pattern_match.rb +48 -0
  17. data/lib/fear/empty_partial_function.rb +36 -0
  18. data/lib/fear/failure.rb +5 -4
  19. data/lib/fear/failure_pattern_match.rb +8 -0
  20. data/lib/fear/for.rb +46 -51
  21. data/lib/fear/left.rb +7 -1
  22. data/lib/fear/left_pattern_match.rb +9 -0
  23. data/lib/fear/none.rb +37 -2
  24. data/lib/fear/none_pattern_match.rb +12 -0
  25. data/lib/fear/option.rb +65 -31
  26. data/lib/fear/option_pattern_match.rb +45 -0
  27. data/lib/fear/partial_function/and_then.rb +48 -0
  28. data/lib/fear/partial_function/any.rb +26 -0
  29. data/lib/fear/partial_function/combined.rb +51 -0
  30. data/lib/fear/partial_function/empty.rb +6 -0
  31. data/lib/fear/partial_function/guard/and.rb +36 -0
  32. data/lib/fear/partial_function/guard/and3.rb +39 -0
  33. data/lib/fear/partial_function/guard/or.rb +36 -0
  34. data/lib/fear/partial_function/guard.rb +90 -0
  35. data/lib/fear/partial_function/lifted.rb +20 -0
  36. data/lib/fear/partial_function/or_else.rb +62 -0
  37. data/lib/fear/partial_function.rb +171 -0
  38. data/lib/fear/partial_function_class.rb +26 -0
  39. data/lib/fear/pattern_match.rb +102 -0
  40. data/lib/fear/pattern_matching_api.rb +110 -0
  41. data/lib/fear/right.rb +7 -1
  42. data/lib/fear/right_biased.rb +2 -12
  43. data/lib/fear/right_pattern_match.rb +9 -0
  44. data/lib/fear/some.rb +5 -2
  45. data/lib/fear/some_pattern_match.rb +11 -0
  46. data/lib/fear/success.rb +5 -4
  47. data/lib/fear/success_pattern_match.rb +10 -0
  48. data/lib/fear/try.rb +56 -16
  49. data/lib/fear/try_pattern_match.rb +28 -0
  50. data/lib/fear/utils.rb +24 -14
  51. data/lib/fear/version.rb +1 -1
  52. data/lib/fear.rb +21 -4
  53. data/spec/fear/either_pattern_match_spec.rb +37 -0
  54. data/spec/fear/failure_spec.rb +41 -3
  55. data/spec/fear/for_spec.rb +17 -29
  56. data/spec/fear/guard_spec.rb +101 -0
  57. data/spec/fear/left_spec.rb +38 -0
  58. data/spec/fear/none_spec.rb +80 -0
  59. data/spec/fear/option_pattern_match_spec.rb +35 -0
  60. data/spec/fear/partial_function/empty_spec.rb +36 -0
  61. data/spec/fear/partial_function_and_then_spec.rb +145 -0
  62. data/spec/fear/partial_function_composition_spec.rb +80 -0
  63. data/spec/fear/partial_function_or_else_spec.rb +274 -0
  64. data/spec/fear/partial_function_spec.rb +165 -0
  65. data/spec/fear/pattern_match_spec.rb +59 -0
  66. data/spec/fear/right_biased/left.rb +1 -6
  67. data/spec/fear/right_biased/right.rb +0 -5
  68. data/spec/fear/right_spec.rb +38 -0
  69. data/spec/fear/some_spec.rb +37 -0
  70. data/spec/fear/success_spec.rb +41 -4
  71. data/spec/fear/try_pattern_match_spec.rb +37 -0
  72. metadata +97 -23
  73. 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(_, reduce_right)
59
+ def reduce(_reduce_left, reduce_right)
54
60
  reduce_right.call(value)
55
61
  end
56
62
 
@@ -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.new
164
+ None
175
165
  end
176
166
 
177
167
  # @return [false]
@@ -0,0 +1,9 @@
1
+ module Fear
2
+ # @api private
3
+ class RightPatternMatch < EitherPatternMatch
4
+ def left(*)
5
+ self
6
+ end
7
+ alias failure left
8
+ end
9
+ end
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.new
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.new
44
+ None
42
45
  else
43
46
  self
44
47
  end
@@ -0,0 +1,11 @@
1
+ module Fear
2
+ # @api private
3
+ class SomePatternMatch < OptionPatternMatch
4
+ # @return [Fear::OptionPatternMatch]
5
+ def none
6
+ self
7
+ end
8
+ end
9
+
10
+ private_constant :SomePatternMatch
11
+ 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
- fail NoSuchElementError, "Predicate does not hold for `#{value}`"
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
 
@@ -0,0 +1,10 @@
1
+ module Fear
2
+ # @api private
3
+ class SuccessPatternMatch < Fear::TryPatternMatch
4
+ # @param conditions [<#==>]
5
+ # @return [Fear::TryPatternMatch]
6
+ def failure(*_conditions)
7
+ self
8
+ end
9
+ end
10
+ end
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
- # if problem.success?
19
- # puts "Result of #{dividend.get} / #{divisor.get} is: #{problem.get}"
20
- # else
21
- # puts "You must've divided by zero or entered something wrong. Try again"
22
- # puts "Info from the exception: #{problem.exception.message}"
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| fail }
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| fail }
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