fear 0.9.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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