fear 0.9.0 → 1.2.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 (155) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/rubocop.yml +39 -0
  3. data/.github/workflows/spec.yml +42 -0
  4. data/.gitignore +0 -1
  5. data/.rubocop.yml +4 -12
  6. data/.simplecov +17 -0
  7. data/CHANGELOG.md +40 -0
  8. data/Gemfile +5 -2
  9. data/Gemfile.lock +130 -0
  10. data/LICENSE.txt +1 -1
  11. data/README.md +1293 -97
  12. data/Rakefile +369 -1
  13. data/benchmarks/README.md +1 -0
  14. data/benchmarks/dry_do_vs_fear_for.txt +11 -0
  15. data/benchmarks/dry_some_fmap_vs_fear_some_map.txt +11 -0
  16. data/benchmarks/factorial.txt +16 -0
  17. data/benchmarks/fear_gaurd_and1_vs_new.txt +13 -0
  18. data/benchmarks/fear_gaurd_and2_vs_and.txt +13 -0
  19. data/benchmarks/fear_gaurd_and3_vs_and_and.txt +13 -0
  20. data/benchmarks/fear_pattern_extracting_with_vs_without_cache.txt +11 -0
  21. data/benchmarks/fear_pattern_matching_construction_vs_execution.txt +13 -0
  22. data/benchmarks/pattern_matching_dry_vs_qo_vs_fear_try.txt +14 -0
  23. data/benchmarks/pattern_matching_qo_vs_fear_pattern_extraction.txt +11 -0
  24. data/benchmarks/pattern_matching_qo_vs_fear_try_execution.txt +11 -0
  25. data/examples/pattern_extracting.rb +17 -0
  26. data/examples/pattern_extracting_ruby2.7.rb +15 -0
  27. data/examples/pattern_matching_binary_tree_set.rb +101 -0
  28. data/examples/pattern_matching_number_in_words.rb +60 -0
  29. data/fear.gemspec +34 -23
  30. data/lib/dry/types/fear.rb +8 -0
  31. data/lib/dry/types/fear/option.rb +125 -0
  32. data/lib/fear.rb +65 -15
  33. data/lib/fear/await.rb +33 -0
  34. data/lib/fear/awaitable.rb +28 -0
  35. data/lib/fear/either.rb +131 -71
  36. data/lib/fear/either_api.rb +23 -0
  37. data/lib/fear/either_pattern_match.rb +53 -0
  38. data/lib/fear/empty_partial_function.rb +38 -0
  39. data/lib/fear/extractor.rb +112 -0
  40. data/lib/fear/extractor/anonymous_array_splat_matcher.rb +10 -0
  41. data/lib/fear/extractor/any_matcher.rb +17 -0
  42. data/lib/fear/extractor/array_head_matcher.rb +36 -0
  43. data/lib/fear/extractor/array_matcher.rb +40 -0
  44. data/lib/fear/extractor/array_splat_matcher.rb +16 -0
  45. data/lib/fear/extractor/empty_list_matcher.rb +20 -0
  46. data/lib/fear/extractor/extractor_matcher.rb +44 -0
  47. data/lib/fear/extractor/grammar.rb +203 -0
  48. data/lib/fear/extractor/grammar.treetop +129 -0
  49. data/lib/fear/extractor/identifier_matcher.rb +18 -0
  50. data/lib/fear/extractor/matcher.rb +53 -0
  51. data/lib/fear/extractor/matcher/and.rb +38 -0
  52. data/lib/fear/extractor/named_array_splat_matcher.rb +17 -0
  53. data/lib/fear/extractor/pattern.rb +58 -0
  54. data/lib/fear/extractor/typed_identifier_matcher.rb +26 -0
  55. data/lib/fear/extractor/value_matcher.rb +19 -0
  56. data/lib/fear/extractor_api.rb +35 -0
  57. data/lib/fear/failure.rb +46 -14
  58. data/lib/fear/failure_pattern_match.rb +10 -0
  59. data/lib/fear/for.rb +37 -95
  60. data/lib/fear/for_api.rb +68 -0
  61. data/lib/fear/future.rb +497 -0
  62. data/lib/fear/future_api.rb +21 -0
  63. data/lib/fear/left.rb +19 -2
  64. data/lib/fear/left_pattern_match.rb +11 -0
  65. data/lib/fear/none.rb +67 -3
  66. data/lib/fear/none_pattern_match.rb +14 -0
  67. data/lib/fear/option.rb +120 -56
  68. data/lib/fear/option_api.rb +40 -0
  69. data/lib/fear/option_pattern_match.rb +48 -0
  70. data/lib/fear/partial_function.rb +176 -0
  71. data/lib/fear/partial_function/and_then.rb +50 -0
  72. data/lib/fear/partial_function/any.rb +28 -0
  73. data/lib/fear/partial_function/combined.rb +53 -0
  74. data/lib/fear/partial_function/empty.rb +10 -0
  75. data/lib/fear/partial_function/guard.rb +80 -0
  76. data/lib/fear/partial_function/guard/and.rb +38 -0
  77. data/lib/fear/partial_function/guard/and3.rb +41 -0
  78. data/lib/fear/partial_function/guard/or.rb +38 -0
  79. data/lib/fear/partial_function/lifted.rb +23 -0
  80. data/lib/fear/partial_function/or_else.rb +64 -0
  81. data/lib/fear/partial_function_class.rb +38 -0
  82. data/lib/fear/pattern_match.rb +114 -0
  83. data/lib/fear/pattern_matching_api.rb +137 -0
  84. data/lib/fear/promise.rb +95 -0
  85. data/lib/fear/right.rb +20 -2
  86. data/lib/fear/right_biased.rb +6 -14
  87. data/lib/fear/right_pattern_match.rb +11 -0
  88. data/lib/fear/some.rb +55 -3
  89. data/lib/fear/some_pattern_match.rb +13 -0
  90. data/lib/fear/struct.rb +248 -0
  91. data/lib/fear/success.rb +35 -5
  92. data/lib/fear/success_pattern_match.rb +12 -0
  93. data/lib/fear/try.rb +136 -79
  94. data/lib/fear/try_api.rb +33 -0
  95. data/lib/fear/try_pattern_match.rb +33 -0
  96. data/lib/fear/unit.rb +32 -0
  97. data/lib/fear/utils.rb +39 -14
  98. data/lib/fear/version.rb +4 -1
  99. data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
  100. data/spec/dry/types/fear/option/core_spec.rb +77 -0
  101. data/spec/dry/types/fear/option/default_spec.rb +21 -0
  102. data/spec/dry/types/fear/option/hash_spec.rb +58 -0
  103. data/spec/dry/types/fear/option/option_spec.rb +97 -0
  104. data/spec/fear/awaitable_spec.rb +17 -0
  105. data/spec/fear/done_spec.rb +8 -6
  106. data/spec/fear/either/mixin_spec.rb +17 -0
  107. data/spec/fear/either_pattern_match_spec.rb +37 -0
  108. data/spec/fear/either_pattern_matching_spec.rb +28 -0
  109. data/spec/fear/extractor/array_matcher_spec.rb +230 -0
  110. data/spec/fear/extractor/extractor_matcher_spec.rb +153 -0
  111. data/spec/fear/extractor/grammar_array_spec.rb +25 -0
  112. data/spec/fear/extractor/identified_matcher_spec.rb +49 -0
  113. data/spec/fear/extractor/identifier_matcher_spec.rb +68 -0
  114. data/spec/fear/extractor/pattern_spec.rb +34 -0
  115. data/spec/fear/extractor/typed_identifier_matcher_spec.rb +64 -0
  116. data/spec/fear/extractor/value_matcher_number_spec.rb +79 -0
  117. data/spec/fear/extractor/value_matcher_string_spec.rb +88 -0
  118. data/spec/fear/extractor/value_matcher_symbol_spec.rb +71 -0
  119. data/spec/fear/extractor_api_spec.rb +115 -0
  120. data/spec/fear/extractor_spec.rb +61 -0
  121. data/spec/fear/failure_spec.rb +145 -45
  122. data/spec/fear/for_spec.rb +57 -67
  123. data/spec/fear/future_spec.rb +691 -0
  124. data/spec/fear/guard_spec.rb +103 -0
  125. data/spec/fear/left_spec.rb +112 -46
  126. data/spec/fear/none_spec.rb +114 -16
  127. data/spec/fear/option/mixin_spec.rb +39 -0
  128. data/spec/fear/option_pattern_match_spec.rb +35 -0
  129. data/spec/fear/option_pattern_matching_spec.rb +34 -0
  130. data/spec/fear/option_spec.rb +121 -8
  131. data/spec/fear/partial_function/empty_spec.rb +38 -0
  132. data/spec/fear/partial_function_and_then_spec.rb +147 -0
  133. data/spec/fear/partial_function_composition_spec.rb +82 -0
  134. data/spec/fear/partial_function_or_else_spec.rb +276 -0
  135. data/spec/fear/partial_function_spec.rb +239 -0
  136. data/spec/fear/pattern_match_spec.rb +93 -0
  137. data/spec/fear/pattern_matching_api_spec.rb +31 -0
  138. data/spec/fear/promise_spec.rb +96 -0
  139. data/spec/fear/right_biased/left.rb +29 -32
  140. data/spec/fear/right_biased/right.rb +51 -54
  141. data/spec/fear/right_spec.rb +109 -41
  142. data/spec/fear/some_spec.rb +80 -15
  143. data/spec/fear/success_spec.rb +99 -32
  144. data/spec/fear/try/mixin_spec.rb +19 -0
  145. data/spec/fear/try_pattern_match_spec.rb +37 -0
  146. data/spec/fear/try_pattern_matching_spec.rb +34 -0
  147. data/spec/fear/utils_spec.rb +16 -14
  148. data/spec/spec_helper.rb +13 -7
  149. data/spec/struct_pattern_matching_spec.rb +36 -0
  150. data/spec/struct_spec.rb +226 -0
  151. data/spec/support/dry_types.rb +6 -0
  152. metadata +320 -29
  153. data/.travis.yml +0 -9
  154. data/lib/fear/done.rb +0 -22
  155. data/lib/fear/for/evaluation_context.rb +0 -91
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module OptionApi
5
+ # An +Option+ factory which creates +Some+ if the argument is
6
+ # not +nil+, and +None+ if it is +nil+.
7
+ # @param value [any]
8
+ # @return [Fear::Some, Fear::None]
9
+ #
10
+ # @example
11
+ # Fear.option(nil) #=> #<Fear::None>
12
+ # Fear.option(17) #=> #<Fear::Some get=17>
13
+ #
14
+ def option(value)
15
+ if value.nil?
16
+ none
17
+ else
18
+ some(value)
19
+ end
20
+ end
21
+
22
+ # @return [Fear::None]
23
+ # @example
24
+ # Fear.none #=> #<Fear::None>
25
+ #
26
+ def none
27
+ Fear::None
28
+ end
29
+
30
+ # @param value [any]
31
+ # @return [Fear::Some]
32
+ # @example
33
+ # Fear.some(17) #=> #<Fear::Some get=17>
34
+ # Fear.some(nil) #=> #<Fear::Some get=nil>
35
+ #
36
+ def some(value)
37
+ Fear::Some.new(value)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ # Option pattern matcher
5
+ #
6
+ # @example
7
+ # pattern_match =
8
+ # OptionPatternMatch.new
9
+ # .some(Integer) { |x| x * 2 }
10
+ # .some(String) { |x| x.to_i * 2 }
11
+ # .none { 'NaN' }
12
+ # .else { 'error '}
13
+ #
14
+ # pattern_match.call(42) => 'NaN'
15
+ #
16
+ # @example the same matcher may be defined using block syntax
17
+ # OptionPatternMatch.new do |m|
18
+ # m.some(Integer) { |x| x * 2 }
19
+ # m.some(String) { |x| x.to_i * 2 }
20
+ # m.none { 'NaN' }
21
+ # m.else { 'error '}
22
+ # end
23
+ #
24
+ # @note it has two optimized subclasses +Fear::SomePatternMatch+ and +Fear::NonePatternMatch+
25
+ # @api private
26
+ class OptionPatternMatch < Fear::PatternMatch
27
+ GET_METHOD = :get.to_proc
28
+ private_constant :GET_METHOD
29
+
30
+ # Match against Some
31
+ #
32
+ # @param conditions [<#==>]
33
+ # @return [Fear::OptionPatternMatch]
34
+ def some(*conditions, &effect)
35
+ branch = Fear.case(Fear::Some, &GET_METHOD).and_then(Fear.case(*conditions, &effect))
36
+ or_else(branch)
37
+ end
38
+
39
+ # Match against None
40
+ #
41
+ # @param effect [Proc]
42
+ # @return [Fear::OptionPatternMatch]
43
+ def none(&effect)
44
+ branch = Fear.case(Fear::None, &effect)
45
+ or_else(branch)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ # A partial function is a unary function defined on subset of all possible inputs.
5
+ # The method +defined_at?+ allows to test dynamically if an arg is in
6
+ # the domain of the function.
7
+ #
8
+ # Even if +defined_at?+ returns true for given arg, calling +call+ may
9
+ # still throw an exception, so the following code is legal:
10
+ #
11
+ # @example
12
+ # Fear.case(->(_) { true }) { 1/0 }
13
+ #
14
+ # It is the responsibility of the caller to call +defined_at?+ before
15
+ # calling +call+, because if +defined_at?+ is false, it is not guaranteed
16
+ # +call+ will throw an exception to indicate an error guard. If an
17
+ # exception is not thrown, evaluation may result in an arbitrary arg.
18
+ #
19
+ # The main distinction between +PartialFunction+ and +Proc+ is
20
+ # that the user of a +PartialFunction+ may choose to do something different
21
+ # with input that is declared to be outside its domain. For example:
22
+ #
23
+ # @example
24
+ # sample = 1...10
25
+ #
26
+ # is_even = Fear.case(->(arg) { arg % 2 == 0}) do |arg|
27
+ # "#{arg} is even"
28
+ # end
29
+ #
30
+ # is_odd = Fear.case(->(arg) { arg % 2 == 1}) do |arg|
31
+ # "#{arg} is odd"
32
+ # end
33
+ #
34
+ # The method or_else allows chaining another partial function to handle
35
+ # input outside the declared domain
36
+ #
37
+ # numbers = sample.map(is_even.or_else(is_odd).to_proc)
38
+ #
39
+ # @see https://github.com/scala/scala/commit/5050915eb620af3aa43d6ddaae6bbb83ad74900d
40
+ # @!method condition
41
+ # describes the domain of partial function
42
+ # @return [#===]
43
+ # @abstract
44
+ # @!method function
45
+ # @return [#call]
46
+ # @abstract
47
+ module PartialFunction
48
+ autoload :AndThen, "fear/partial_function/and_then"
49
+ autoload :Any, "fear/partial_function/any"
50
+ autoload :Combined, "fear/partial_function/combined"
51
+ autoload :EMPTY, "fear/partial_function/empty"
52
+ autoload :Guard, "fear/partial_function/guard"
53
+ autoload :Lifted, "fear/partial_function/lifted"
54
+ autoload :OrElse, "fear/partial_function/or_else"
55
+
56
+ # Checks if a value is contained in the function's domain.
57
+ #
58
+ # @param arg [any]
59
+ # @return [Boolean]
60
+ def defined_at?(arg)
61
+ condition === arg
62
+ end
63
+
64
+ # @!method call(arg)
65
+ # @param arg [any]
66
+ # @return [any] Calls this partial function with the given argument when it
67
+ # is contained in the function domain.
68
+ # @raise [MatchError] when this partial function is not defined.
69
+ # @abstract
70
+
71
+ # Converts this partial function to other
72
+ #
73
+ # @return [Proc]
74
+ def to_proc
75
+ proc { |arg| call(arg) }
76
+ end
77
+
78
+ # Calls this partial function with the given argument when it is contained in the function domain.
79
+ # Calls fallback function where this partial function is not defined.
80
+ #
81
+ # @param arg [any]
82
+ # @yield [arg] if partial function not defined for this +arg+
83
+ #
84
+ # @note that expression +pf.call_or_else(arg, &fallback)+ is equivalent to
85
+ # +pf.defined_at?(arg) ? pf.(arg) : fallback.(arg)+
86
+ # except that +call_or_else+ method can be implemented more efficiently to avoid calling +defined_at?+ twice.
87
+ #
88
+ def call_or_else(arg)
89
+ if defined_at?(arg)
90
+ call(arg)
91
+ else
92
+ yield arg
93
+ end
94
+ end
95
+
96
+ # Composes this partial function with a fallback partial function which
97
+ # gets applied where this partial function is not defined.
98
+ #
99
+ # @param other [PartialFunction]
100
+ # @return [PartialFunction] a partial function which has as domain the union of the domains
101
+ # of this partial function and +other+.
102
+ # @example
103
+ # handle_even = Fear.case(:even?.to_proc) { |x| "#{x} is even" }
104
+ # handle_odd = Fear.case(:odd?.to_proc) { |x| "#{x} is odd" }
105
+ # handle_even_or_odd = handle_even.or_else(odd)
106
+ # handle_even_or_odd.(42) #=> 42 is even
107
+ # handle_even_or_odd.(42) #=> 21 is odd
108
+ def or_else(other)
109
+ OrElse.new(self, other)
110
+ end
111
+
112
+ # @see or_else
113
+ def |(other)
114
+ or_else(other)
115
+ end
116
+
117
+ # Composes this partial function with a fallback partial function (or Proc) which
118
+ # gets applied where this partial function is not defined.
119
+ #
120
+ # @overload and_then(other)
121
+ # @param other [Fear::PartialFunction]
122
+ # @return [Fear::PartialFunction] a partial function with the same domain as this partial function, which maps
123
+ # argument +x+ to +other.(self.call(x))+.
124
+ # @note calling +#defined_at?+ on the resulting partial function may call the first
125
+ # partial function and execute its side effect. It is highly recommended to call +#call_or_else+
126
+ # instead of +#defined_at?+/+#call+ for efficiency.
127
+ # @overload and_then(other)
128
+ # @param other [Proc]
129
+ # @return [Fear::PartialFunction] a partial function with the same domain as this partial function, which maps
130
+ # argument +x+ to +other.(self.call(x))+.
131
+ # @overload and_then(&other)
132
+ # @param other [Proc]
133
+ # @return [Fear::PartialFunction]
134
+ #
135
+ def and_then(other = Utils::UNDEFINED, &block)
136
+ Utils.with_block_or_argument("Fear::PartialFunction#and_then", other, block) do |fun|
137
+ if fun.is_a?(Fear::PartialFunction)
138
+ Combined.new(self, fun)
139
+ else
140
+ AndThen.new(self, &fun)
141
+ end
142
+ end
143
+ end
144
+
145
+ # @see and_then
146
+ def &(other)
147
+ and_then(other)
148
+ end
149
+
150
+ # Turns this partial function in Proc-like object, returning +Option+
151
+ # @return [#call]
152
+ def lift
153
+ Lifted.new(self)
154
+ end
155
+
156
+ class << self
157
+ # Creates partial function guarded by several condition.
158
+ # All guards should match.
159
+ # @param guards [<#===>]
160
+ # @param function [Proc]
161
+ # @return [Fear::PartialFunction]
162
+ def and(*guards, &function)
163
+ PartialFunctionClass.new(Guard.and(guards), &function)
164
+ end
165
+
166
+ # Creates partial function guarded by several condition.
167
+ # Any condition should match.
168
+ # @param guards [<#===>]
169
+ # @param function [Proc]
170
+ # @return [Fear::PartialFunction]
171
+ def or(*guards, &function)
172
+ PartialFunctionClass.new(Guard.or(guards), &function)
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module PartialFunction
5
+ # Composite function produced by +PartialFunction#and_then+ method
6
+ # @api private
7
+ class AndThen
8
+ include PartialFunction
9
+
10
+ # @param partial_function [Fear::PartialFunction]
11
+ # @param function [Proc]
12
+ def initialize(partial_function, &function)
13
+ @partial_function = partial_function
14
+ @function = function
15
+ end
16
+ # @!attribute partial_function
17
+ # @return [Fear::PartialFunction]
18
+ # @!attribute function
19
+ # @return [Proc]
20
+ attr_reader :partial_function
21
+ attr_reader :function
22
+ private :partial_function
23
+ private :function
24
+
25
+ # @param arg [any]
26
+ # @return [any ]
27
+ def call(arg)
28
+ function.(partial_function.(arg))
29
+ end
30
+
31
+ # @param arg [any]
32
+ # @return [Boolean]
33
+ def defined_at?(arg)
34
+ partial_function.defined_at?(arg)
35
+ end
36
+
37
+ # @param arg [any]
38
+ # @yield [arg]
39
+ # @return [any]
40
+ def call_or_else(arg)
41
+ result = partial_function.call_or_else(arg) do
42
+ return yield(arg)
43
+ end
44
+ function.(result)
45
+ end
46
+ end
47
+
48
+ private_constant :AndThen
49
+ end
50
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module PartialFunction
5
+ # Any is an object which is always truthy
6
+ # @api private
7
+ class Any
8
+ class << self
9
+ # @param _other [any]
10
+ # @return [true]
11
+ def ===(_other)
12
+ true
13
+ end
14
+
15
+ # @param _other [any]
16
+ # @return [true]
17
+ def ==(_other)
18
+ true
19
+ end
20
+
21
+ # @return [Proc]
22
+ def to_proc
23
+ proc { true }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module PartialFunction
5
+ # Composite function produced by +PartialFunction#and_then+ method
6
+ # @api private
7
+ class Combined
8
+ include PartialFunction
9
+
10
+ # @param f1 [Fear::PartialFunction]
11
+ # @param f2 [Fear::PartialFunction]
12
+ def initialize(f1, f2)
13
+ @f1 = f1
14
+ @f2 = f2
15
+ end
16
+ # @!attribute f1
17
+ # @return [Fear::PartialFunction]
18
+ # @!attribute f2
19
+ # @return [Fear::PartialFunction]
20
+ attr_reader :f1, :f2
21
+ private :f1
22
+ private :f2
23
+
24
+ # @param arg [any]
25
+ # @return [any ]
26
+ def call(arg)
27
+ f2.(f1.(arg))
28
+ end
29
+
30
+ alias === call
31
+ alias [] call
32
+
33
+ # @param arg [any]
34
+ # @yieldparam arg [any]
35
+ # @return [any]
36
+ def call_or_else(arg)
37
+ result = f1.call_or_else(arg) { return yield(arg) }
38
+ f2.call_or_else(result) { |_| return yield(arg) }
39
+ end
40
+
41
+ # @param arg [any]
42
+ # @return [Boolean]
43
+ def defined_at?(arg)
44
+ result = f1.call_or_else(arg) do
45
+ return false
46
+ end
47
+ f2.defined_at?(result)
48
+ end
49
+ end
50
+
51
+ private_constant :AndThen
52
+ end
53
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module PartialFunction
5
+ EMPTY = EmptyPartialFunction.new
6
+ EMPTY.freeze
7
+
8
+ public_constant :EMPTY
9
+ end
10
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module PartialFunction
5
+ # Guard represents PartialFunction guardian
6
+ #
7
+ # @api private
8
+ class Guard
9
+ autoload :And, "fear/partial_function/guard/and"
10
+ autoload :And3, "fear/partial_function/guard/and3"
11
+ autoload :Or, "fear/partial_function/guard/or"
12
+
13
+ class << self
14
+ # Optimized version for combination of two guardians
15
+ # Two guarding is a very common situation. For example checking for Some, and checking
16
+ # a value withing container.
17
+ #
18
+ def and2(c1, c2)
19
+ Guard::And.new(c1, c2)
20
+ end
21
+
22
+ def and3(c1, c2, c3)
23
+ Guard::And3.new(c1, c2, c3)
24
+ end
25
+
26
+ def and1(c)
27
+ c
28
+ end
29
+
30
+ # @param conditions [<#===>]
31
+ # @return [Fear::PartialFunction::Guard]
32
+ def and(conditions)
33
+ case conditions.size
34
+ when 1 then and1(*conditions)
35
+ when 2 then and2(*conditions)
36
+ when 3 then and3(*conditions)
37
+ when 0 then Any
38
+ else
39
+ head, *tail = conditions
40
+ tail.reduce(new(head)) { |acc, condition| acc.and(new(condition)) }
41
+ end
42
+ end
43
+
44
+ # @param conditions [<#===>]
45
+ # @return [Fear::PartialFunction::Guard]
46
+ def or(conditions)
47
+ return Any if conditions.empty?
48
+
49
+ head, *tail = conditions
50
+ tail.reduce(new(head)) { |acc, condition| acc.or(new(condition)) }
51
+ end
52
+ end
53
+
54
+ # @param condition [#===]
55
+ def initialize(condition)
56
+ @condition = condition
57
+ end
58
+ attr_reader :condition
59
+ private :condition
60
+
61
+ # @param other [Fear::PartialFunction::Guard]
62
+ # @return [Fear::PartialFunction::Guard]
63
+ def and(other)
64
+ Guard::And.new(condition, other)
65
+ end
66
+
67
+ # @param other [Fear::PartialFunction::Guard]
68
+ # @return [Fear::PartialFunction::Guard]
69
+ def or(other)
70
+ Guard::Or.new(condition, other)
71
+ end
72
+
73
+ # @param arg [any]
74
+ # @return [Boolean]
75
+ def ===(arg)
76
+ condition === arg
77
+ end
78
+ end
79
+ end
80
+ end