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