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.
- checksums.yaml +5 -5
- data/.github/workflows/rubocop.yml +39 -0
- data/.github/workflows/spec.yml +42 -0
- data/.gitignore +0 -1
- data/.rubocop.yml +4 -12
- data/.simplecov +17 -0
- data/CHANGELOG.md +40 -0
- data/Gemfile +5 -2
- data/Gemfile.lock +130 -0
- data/LICENSE.txt +1 -1
- data/README.md +1293 -97
- data/Rakefile +369 -1
- data/benchmarks/README.md +1 -0
- data/benchmarks/dry_do_vs_fear_for.txt +11 -0
- data/benchmarks/dry_some_fmap_vs_fear_some_map.txt +11 -0
- data/benchmarks/factorial.txt +16 -0
- data/benchmarks/fear_gaurd_and1_vs_new.txt +13 -0
- data/benchmarks/fear_gaurd_and2_vs_and.txt +13 -0
- data/benchmarks/fear_gaurd_and3_vs_and_and.txt +13 -0
- data/benchmarks/fear_pattern_extracting_with_vs_without_cache.txt +11 -0
- data/benchmarks/fear_pattern_matching_construction_vs_execution.txt +13 -0
- data/benchmarks/pattern_matching_dry_vs_qo_vs_fear_try.txt +14 -0
- data/benchmarks/pattern_matching_qo_vs_fear_pattern_extraction.txt +11 -0
- data/benchmarks/pattern_matching_qo_vs_fear_try_execution.txt +11 -0
- data/examples/pattern_extracting.rb +17 -0
- data/examples/pattern_extracting_ruby2.7.rb +15 -0
- data/examples/pattern_matching_binary_tree_set.rb +101 -0
- data/examples/pattern_matching_number_in_words.rb +60 -0
- data/fear.gemspec +34 -23
- data/lib/dry/types/fear.rb +8 -0
- data/lib/dry/types/fear/option.rb +125 -0
- data/lib/fear.rb +65 -15
- data/lib/fear/await.rb +33 -0
- data/lib/fear/awaitable.rb +28 -0
- data/lib/fear/either.rb +131 -71
- data/lib/fear/either_api.rb +23 -0
- data/lib/fear/either_pattern_match.rb +53 -0
- data/lib/fear/empty_partial_function.rb +38 -0
- data/lib/fear/extractor.rb +112 -0
- data/lib/fear/extractor/anonymous_array_splat_matcher.rb +10 -0
- data/lib/fear/extractor/any_matcher.rb +17 -0
- data/lib/fear/extractor/array_head_matcher.rb +36 -0
- data/lib/fear/extractor/array_matcher.rb +40 -0
- data/lib/fear/extractor/array_splat_matcher.rb +16 -0
- data/lib/fear/extractor/empty_list_matcher.rb +20 -0
- data/lib/fear/extractor/extractor_matcher.rb +44 -0
- data/lib/fear/extractor/grammar.rb +203 -0
- data/lib/fear/extractor/grammar.treetop +129 -0
- data/lib/fear/extractor/identifier_matcher.rb +18 -0
- data/lib/fear/extractor/matcher.rb +53 -0
- data/lib/fear/extractor/matcher/and.rb +38 -0
- data/lib/fear/extractor/named_array_splat_matcher.rb +17 -0
- data/lib/fear/extractor/pattern.rb +58 -0
- data/lib/fear/extractor/typed_identifier_matcher.rb +26 -0
- data/lib/fear/extractor/value_matcher.rb +19 -0
- data/lib/fear/extractor_api.rb +35 -0
- data/lib/fear/failure.rb +46 -14
- data/lib/fear/failure_pattern_match.rb +10 -0
- data/lib/fear/for.rb +37 -95
- data/lib/fear/for_api.rb +68 -0
- data/lib/fear/future.rb +497 -0
- data/lib/fear/future_api.rb +21 -0
- data/lib/fear/left.rb +19 -2
- data/lib/fear/left_pattern_match.rb +11 -0
- data/lib/fear/none.rb +67 -3
- data/lib/fear/none_pattern_match.rb +14 -0
- data/lib/fear/option.rb +120 -56
- data/lib/fear/option_api.rb +40 -0
- data/lib/fear/option_pattern_match.rb +48 -0
- data/lib/fear/partial_function.rb +176 -0
- data/lib/fear/partial_function/and_then.rb +50 -0
- data/lib/fear/partial_function/any.rb +28 -0
- data/lib/fear/partial_function/combined.rb +53 -0
- data/lib/fear/partial_function/empty.rb +10 -0
- data/lib/fear/partial_function/guard.rb +80 -0
- data/lib/fear/partial_function/guard/and.rb +38 -0
- data/lib/fear/partial_function/guard/and3.rb +41 -0
- data/lib/fear/partial_function/guard/or.rb +38 -0
- data/lib/fear/partial_function/lifted.rb +23 -0
- data/lib/fear/partial_function/or_else.rb +64 -0
- data/lib/fear/partial_function_class.rb +38 -0
- data/lib/fear/pattern_match.rb +114 -0
- data/lib/fear/pattern_matching_api.rb +137 -0
- data/lib/fear/promise.rb +95 -0
- data/lib/fear/right.rb +20 -2
- data/lib/fear/right_biased.rb +6 -14
- data/lib/fear/right_pattern_match.rb +11 -0
- data/lib/fear/some.rb +55 -3
- data/lib/fear/some_pattern_match.rb +13 -0
- data/lib/fear/struct.rb +248 -0
- data/lib/fear/success.rb +35 -5
- data/lib/fear/success_pattern_match.rb +12 -0
- data/lib/fear/try.rb +136 -79
- data/lib/fear/try_api.rb +33 -0
- data/lib/fear/try_pattern_match.rb +33 -0
- data/lib/fear/unit.rb +32 -0
- data/lib/fear/utils.rb +39 -14
- data/lib/fear/version.rb +4 -1
- data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
- data/spec/dry/types/fear/option/core_spec.rb +77 -0
- data/spec/dry/types/fear/option/default_spec.rb +21 -0
- data/spec/dry/types/fear/option/hash_spec.rb +58 -0
- data/spec/dry/types/fear/option/option_spec.rb +97 -0
- data/spec/fear/awaitable_spec.rb +17 -0
- data/spec/fear/done_spec.rb +8 -6
- data/spec/fear/either/mixin_spec.rb +17 -0
- data/spec/fear/either_pattern_match_spec.rb +37 -0
- data/spec/fear/either_pattern_matching_spec.rb +28 -0
- data/spec/fear/extractor/array_matcher_spec.rb +230 -0
- data/spec/fear/extractor/extractor_matcher_spec.rb +153 -0
- data/spec/fear/extractor/grammar_array_spec.rb +25 -0
- data/spec/fear/extractor/identified_matcher_spec.rb +49 -0
- data/spec/fear/extractor/identifier_matcher_spec.rb +68 -0
- data/spec/fear/extractor/pattern_spec.rb +34 -0
- data/spec/fear/extractor/typed_identifier_matcher_spec.rb +64 -0
- data/spec/fear/extractor/value_matcher_number_spec.rb +79 -0
- data/spec/fear/extractor/value_matcher_string_spec.rb +88 -0
- data/spec/fear/extractor/value_matcher_symbol_spec.rb +71 -0
- data/spec/fear/extractor_api_spec.rb +115 -0
- data/spec/fear/extractor_spec.rb +61 -0
- data/spec/fear/failure_spec.rb +145 -45
- data/spec/fear/for_spec.rb +57 -67
- data/spec/fear/future_spec.rb +691 -0
- data/spec/fear/guard_spec.rb +103 -0
- data/spec/fear/left_spec.rb +112 -46
- data/spec/fear/none_spec.rb +114 -16
- data/spec/fear/option/mixin_spec.rb +39 -0
- data/spec/fear/option_pattern_match_spec.rb +35 -0
- data/spec/fear/option_pattern_matching_spec.rb +34 -0
- data/spec/fear/option_spec.rb +121 -8
- data/spec/fear/partial_function/empty_spec.rb +38 -0
- data/spec/fear/partial_function_and_then_spec.rb +147 -0
- data/spec/fear/partial_function_composition_spec.rb +82 -0
- data/spec/fear/partial_function_or_else_spec.rb +276 -0
- data/spec/fear/partial_function_spec.rb +239 -0
- data/spec/fear/pattern_match_spec.rb +93 -0
- data/spec/fear/pattern_matching_api_spec.rb +31 -0
- data/spec/fear/promise_spec.rb +96 -0
- data/spec/fear/right_biased/left.rb +29 -32
- data/spec/fear/right_biased/right.rb +51 -54
- data/spec/fear/right_spec.rb +109 -41
- data/spec/fear/some_spec.rb +80 -15
- data/spec/fear/success_spec.rb +99 -32
- data/spec/fear/try/mixin_spec.rb +19 -0
- data/spec/fear/try_pattern_match_spec.rb +37 -0
- data/spec/fear/try_pattern_matching_spec.rb +34 -0
- data/spec/fear/utils_spec.rb +16 -14
- data/spec/spec_helper.rb +13 -7
- data/spec/struct_pattern_matching_spec.rb +36 -0
- data/spec/struct_spec.rb +226 -0
- data/spec/support/dry_types.rb +6 -0
- metadata +320 -29
- data/.travis.yml +0 -9
- data/lib/fear/done.rb +0 -22
- 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,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
|