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,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fear
|
4
|
+
module PartialFunction
|
5
|
+
class Guard
|
6
|
+
# @api private
|
7
|
+
class And < Guard
|
8
|
+
# @param c1 [Fear::PartialFunction::Guard]
|
9
|
+
# @param c2 [Fear::PartialFunction::Guard]
|
10
|
+
def initialize(c1, c2)
|
11
|
+
@c1 = c1
|
12
|
+
@c2 = c2
|
13
|
+
end
|
14
|
+
attr_reader :c1, :c2
|
15
|
+
private :c1
|
16
|
+
private :c2
|
17
|
+
|
18
|
+
# @param other [Fear::PartialFunction::Guard]
|
19
|
+
# @return [Fear::PartialFunction::Guard]
|
20
|
+
def and(other)
|
21
|
+
Guard::And.new(self, other)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param other [Fear::PartialFunction::Guard]
|
25
|
+
# @return [Fear::PartialFunction::Guard]
|
26
|
+
def or(other)
|
27
|
+
Guard::Or.new(self, other)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param arg [any]
|
31
|
+
# @return [Boolean]
|
32
|
+
def ===(arg)
|
33
|
+
(c1 === arg) && (c2 === arg)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fear
|
4
|
+
module PartialFunction
|
5
|
+
class Guard
|
6
|
+
# @api private
|
7
|
+
class And3 < Guard
|
8
|
+
# @param c1 [#===]
|
9
|
+
# @param c2 [#===]
|
10
|
+
# @param c3 [#===]
|
11
|
+
def initialize(c1, c2, c3)
|
12
|
+
@c1 = c1
|
13
|
+
@c2 = c2
|
14
|
+
@c3 = c3
|
15
|
+
end
|
16
|
+
attr_reader :c1, :c2, :c3
|
17
|
+
private :c1
|
18
|
+
private :c2
|
19
|
+
private :c3
|
20
|
+
|
21
|
+
# @param other [Fear::PartialFunction::Guard]
|
22
|
+
# @return [Fear::PartialFunction::Guard]
|
23
|
+
def and(other)
|
24
|
+
Guard::And.new(self, other)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param other [Fear::PartialFunction::Guard]
|
28
|
+
# @return [Fear::PartialFunction::Guard]
|
29
|
+
def or(other)
|
30
|
+
Guard::Or.new(self, other)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param arg [any]
|
34
|
+
# @return [Boolean]
|
35
|
+
def ===(arg)
|
36
|
+
(c1 === arg) && (c2 === arg) && (c3 === arg)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fear
|
4
|
+
module PartialFunction
|
5
|
+
class Guard
|
6
|
+
# @api private
|
7
|
+
class Or < Guard
|
8
|
+
# @param c1 [Fear::PartialFunction::Guard]
|
9
|
+
# @param c2 [Fear::PartialFunction::Guard]
|
10
|
+
def initialize(c1, c2)
|
11
|
+
@c1 = c1
|
12
|
+
@c2 = c2
|
13
|
+
end
|
14
|
+
attr_reader :c1, :c2
|
15
|
+
private :c1
|
16
|
+
private :c2
|
17
|
+
|
18
|
+
# @param other [Fear::PartialFunction::Guard]
|
19
|
+
# @return [Fear::PartialFunction::Guard]
|
20
|
+
def and(other)
|
21
|
+
Guard::And.new(self, other)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param other [Fear::PartialFunction::Guard]
|
25
|
+
# @return [Fear::PartialFunction::Guard]
|
26
|
+
def or(other)
|
27
|
+
Guard::Or.new(self, other)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param arg [any]
|
31
|
+
# @return [Boolean]
|
32
|
+
def ===(arg)
|
33
|
+
(c1 === arg) || (c2 === arg)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fear
|
4
|
+
module PartialFunction
|
5
|
+
# @api private
|
6
|
+
class Lifted
|
7
|
+
# @param pf [Fear::PartialFunction]
|
8
|
+
def initialize(pf)
|
9
|
+
@pf = pf
|
10
|
+
end
|
11
|
+
attr_reader :pf
|
12
|
+
private :pf
|
13
|
+
|
14
|
+
# @param arg [any]
|
15
|
+
# @return [Fear::Option]
|
16
|
+
def call(arg)
|
17
|
+
Some.new(pf.call_or_else(arg) { return Fear::None })
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private_constant :Lifted
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fear
|
4
|
+
module PartialFunction
|
5
|
+
# Composite function produced by +PartialFunction#or_else+ method
|
6
|
+
# @api private
|
7
|
+
class OrElse
|
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
|
+
f1.call_or_else(arg, &f2)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias === call
|
31
|
+
alias [] call
|
32
|
+
|
33
|
+
# @param other [Fear::PartialFunction]
|
34
|
+
# @return [Fear::PartialFunction]
|
35
|
+
def or_else(other)
|
36
|
+
OrElse.new(f1, f2.or_else(other))
|
37
|
+
end
|
38
|
+
|
39
|
+
# @see Fear::PartialFunction#and_then
|
40
|
+
def and_then(other = Utils::UNDEFINED, &block)
|
41
|
+
Utils.with_block_or_argument("Fear::PartialFunction::OrElse#and_then", other, block) do |fun|
|
42
|
+
OrElse.new(f1.and_then(&fun), f2.and_then(&fun))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param arg [any]
|
47
|
+
# @return [Boolean]
|
48
|
+
def defined_at?(arg)
|
49
|
+
f1.defined_at?(arg) || f2.defined_at?(arg)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param arg [any]
|
53
|
+
# @param fallback [Proc]
|
54
|
+
# @return [any]
|
55
|
+
def call_or_else(arg, &fallback)
|
56
|
+
f1.call_or_else(arg) do
|
57
|
+
return f2.call_or_else(arg, &fallback)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private_constant :OrElse
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fear
|
4
|
+
# @api private
|
5
|
+
class PartialFunctionClass
|
6
|
+
include PartialFunction
|
7
|
+
|
8
|
+
# @param condition [#call] describes the domain of partial function
|
9
|
+
# @param function [Proc] function definition
|
10
|
+
def initialize(condition, &function)
|
11
|
+
@condition = condition
|
12
|
+
@function = function
|
13
|
+
end
|
14
|
+
attr_reader :condition, :function
|
15
|
+
private :condition
|
16
|
+
private :function
|
17
|
+
|
18
|
+
# @param arg [any]
|
19
|
+
# @return [any] Calls this partial function with the given argument when it
|
20
|
+
# is contained in the function domain.
|
21
|
+
# @raise [MatchError] when this partial function is not defined.
|
22
|
+
def call(arg)
|
23
|
+
call_or_else(arg, &PartialFunction::EMPTY)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param arg [any]
|
27
|
+
# @yield [arg] if function not defined
|
28
|
+
def call_or_else(arg)
|
29
|
+
if defined_at?(arg)
|
30
|
+
function.(arg)
|
31
|
+
else
|
32
|
+
yield arg
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private_constant :PartialFunctionClass
|
38
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fear
|
4
|
+
# Pattern match builder. Provides DSL for building pattern matcher
|
5
|
+
# Pattern match is just a combination of partial functions
|
6
|
+
#
|
7
|
+
# matcher = Fear.matcher do |m|
|
8
|
+
# m.case(Integer) { |x| x * 2 }
|
9
|
+
# m.case(String) { |x| x.to_i(10) * 2 }
|
10
|
+
# end
|
11
|
+
# matcher.is_a?(Fear::PartialFunction) #=> true
|
12
|
+
# matcher.defined_at?(4) #=> true
|
13
|
+
# matcher.defined_at?('4') #=> true
|
14
|
+
# matcher.defined_at?(nil) #=> false
|
15
|
+
#
|
16
|
+
# The previous example is the same as:
|
17
|
+
#
|
18
|
+
# Fear.case(Integer) { |x| x * ) }
|
19
|
+
# .or_else(
|
20
|
+
# Fear.case(String) { |x| x.to_i(10) * 2 }
|
21
|
+
# )
|
22
|
+
#
|
23
|
+
# You can provide +else+ branch, so partial function will be defined
|
24
|
+
# on any input:
|
25
|
+
#
|
26
|
+
# matcher = Fear.matcher do |m|
|
27
|
+
# m.else { 'Match' }
|
28
|
+
# end
|
29
|
+
# matcher.call(42) #=> 'Match'
|
30
|
+
#
|
31
|
+
# @see Fear.matcher public interface for building matchers
|
32
|
+
# @api Fear
|
33
|
+
# @note Use this class only to build custom pattern match classes. See +Fear::OptionPatternMatch+ as an example.
|
34
|
+
class PatternMatch
|
35
|
+
class << self
|
36
|
+
alias __new__ new
|
37
|
+
|
38
|
+
# @return [Fear::PartialFunction]
|
39
|
+
def new
|
40
|
+
builder = __new__(PartialFunction::EMPTY)
|
41
|
+
yield builder
|
42
|
+
builder.result
|
43
|
+
end
|
44
|
+
|
45
|
+
# Creates anonymous module to add `#mathing` behaviour to a class
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# class User
|
49
|
+
# include Fear::PatternMatch.mixin
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# user.match do |m\
|
53
|
+
# m.case(:admin?) { |u| ... }
|
54
|
+
# m.else { |u| ... }
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# @param as [Symbol, String] (:match) method name
|
58
|
+
# @return [Module]
|
59
|
+
def mixin(as: :match)
|
60
|
+
matcher_class = self
|
61
|
+
|
62
|
+
Module.new do
|
63
|
+
define_method(as) do |&matchers|
|
64
|
+
matcher_class.new(&matchers).(self)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param result [Fear::EmptyPartialFunction]
|
71
|
+
def initialize(result)
|
72
|
+
@result = result
|
73
|
+
end
|
74
|
+
attr_accessor :result
|
75
|
+
private :result=
|
76
|
+
|
77
|
+
# @see Fear::PartialFunction#else
|
78
|
+
def else(&effect)
|
79
|
+
or_else(Fear.case(&effect))
|
80
|
+
end
|
81
|
+
|
82
|
+
# This method is syntactic sugar for `PartialFunction#or_else`, but rather than passing
|
83
|
+
# another partial function as an argument, you pass arguments to build such partial function.
|
84
|
+
# @example This two examples produces the same result
|
85
|
+
# other = Fear.case(Integer) { |x| x * 2 }
|
86
|
+
# this.or_else(other)
|
87
|
+
#
|
88
|
+
# this.case(Integer) { |x| x * 2 }
|
89
|
+
#
|
90
|
+
# @param guards [<#===>]
|
91
|
+
# @param effect [Proc]
|
92
|
+
# @return [Fear::PartialFunction]
|
93
|
+
# @see #or_else for details
|
94
|
+
def case(*guards, &effect)
|
95
|
+
or_else(Fear.case(*guards, &effect))
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param pattern [String]
|
99
|
+
# @param guards [<#===>]
|
100
|
+
# @param effect [Proc]
|
101
|
+
# @return [Fear::PartialFunction]
|
102
|
+
# @see #case for details
|
103
|
+
# @see Fear.xcase for details
|
104
|
+
def xcase(pattern, *guards, &effect)
|
105
|
+
or_else(Fear.xcase(pattern, *guards, &effect))
|
106
|
+
end
|
107
|
+
|
108
|
+
# @see Fear::PartialFunction#or_else
|
109
|
+
def or_else(other)
|
110
|
+
self.result = result.or_else(other)
|
111
|
+
self
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fear
|
4
|
+
# @api private
|
5
|
+
module PatternMatchingApi
|
6
|
+
# Creates pattern match. Use `case` method to
|
7
|
+
# define matching branches. Branch consist of a
|
8
|
+
# guardian, which describes domain of the
|
9
|
+
# branch and function to apply to matching value.
|
10
|
+
#
|
11
|
+
# @example This mather apply different functions to Integers and to Strings
|
12
|
+
# matcher = Fear.matcher do |m|
|
13
|
+
# m.case(Integer) { |n| "#{n} is a number" }
|
14
|
+
# m.case(String) { |n| "#{n} is a string" }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# matcher.(42) #=> "42 is a number"
|
18
|
+
# matcher.("Foo") #=> "Foo is a string"
|
19
|
+
#
|
20
|
+
# if you pass something other than Integer or string, it will raise `Fear::MatchError`:
|
21
|
+
#
|
22
|
+
# matcher.(10..20) #=> raises Fear::MatchError
|
23
|
+
#
|
24
|
+
# to avoid raising `MatchError`, you can use `else` method. It defines a branch matching
|
25
|
+
# on any value.
|
26
|
+
#
|
27
|
+
# matcher = Fear.matcher do |m|
|
28
|
+
# m.case(Integer) { |n| "#{n} is a number" }
|
29
|
+
# m.case(String) { |n| "#{n} is a string" }
|
30
|
+
# m.else { |n| "#{n} is a #{n.class}" }
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# matcher.(10..20) #=> "10..20 is a Range"
|
34
|
+
#
|
35
|
+
# You can use anything as a guardian if it responds to `#===` method:
|
36
|
+
#
|
37
|
+
# m.case(20..40) { |m| "#{m} is within range" }
|
38
|
+
# m.case(->(x) { x > 10}) { |m| "#{m} is greater than 10" }
|
39
|
+
# m.case(:even?.to_proc) { |x| "#{x} is even" }
|
40
|
+
# m.case(:odd?.to_proc) { |x| "#{x} is odd" }
|
41
|
+
#
|
42
|
+
# It's also possible to pass several guardians. All should match to pass
|
43
|
+
#
|
44
|
+
# m.case(Integer, :even?.to_proc) { |x| ... }
|
45
|
+
# m.case(Integer, :odd?.to_proc) { |x| ... }
|
46
|
+
#
|
47
|
+
# If you want to perform pattern destruction, use +#xcase+ method
|
48
|
+
#
|
49
|
+
# m.xcase('Date(year, 12, 31)') { |year:| "Last day of the year #{year}" }
|
50
|
+
#
|
51
|
+
# The pattern above ensures that it's 31 of December and extracts year to block named parameter
|
52
|
+
#
|
53
|
+
# Since matcher returns +Fear::PartialFunction+, you can combine matchers using
|
54
|
+
# partial function API:
|
55
|
+
#
|
56
|
+
# failures = Fear.matcher do |m|
|
57
|
+
# m.case('not_found') { ... }
|
58
|
+
# m.case('network_error') { ... }
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# success = Fear.matcher do |m|
|
62
|
+
# m.case('ok') { ... }
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# response = failures.or_else(success)
|
66
|
+
#
|
67
|
+
# @yieldparam matcher [Fear::PatternMatch]
|
68
|
+
# @return [Fear::PartialFunction]
|
69
|
+
# @see Fear::OptionPatternMatch for example of custom matcher
|
70
|
+
def matcher(&block)
|
71
|
+
PatternMatch.new(&block)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Pattern match against given value
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# Fear.match(42) do |m|
|
78
|
+
# m.case(Integer, :even?.to_proc) { |n| "#{n} is even number" }
|
79
|
+
# m.case(Integer, :odd?.to_proc) { |n| "#{n} is odd number" }
|
80
|
+
# m.case(Strings) { |n| "#{n} is a string" }
|
81
|
+
# m.else { 'unknown' }
|
82
|
+
# end #=> "42 is even number"
|
83
|
+
#
|
84
|
+
# @param value [any]
|
85
|
+
# @yieldparam matcher [Fear::PartialFunction]
|
86
|
+
# @return [any]
|
87
|
+
def match(value, &block)
|
88
|
+
matcher(&block).(value)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Creates partial function defined on domain described with guards
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# pf = Fear.case(Integer) { |x| x / 2 }
|
95
|
+
# pf.defined_at?(4) #=> true
|
96
|
+
# pf.defined_at?('Foo') #=> false
|
97
|
+
#
|
98
|
+
# @example multiple guards combined using logical "and"
|
99
|
+
# pf = Fear.case(Integer, :even?.to_proc) { |x| x / 2 }
|
100
|
+
# pf.defined_at?(4) #=> true
|
101
|
+
# pf.defined_at?(3) #=> false
|
102
|
+
#
|
103
|
+
# @note to make more complex matches, you are encouraged to use Qo gem.
|
104
|
+
# @see Qo https://github.com/baweaver/qo
|
105
|
+
# @example
|
106
|
+
# Fear.case(Qo[age: 20..30]) { |_| 'old enough' }
|
107
|
+
#
|
108
|
+
# @param guards [<#===>]
|
109
|
+
# @param function [Proc]
|
110
|
+
# @return [Fear::PartialFunction]
|
111
|
+
def case(*guards, &function)
|
112
|
+
PartialFunction.and(*guards, &function)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Creates partial function defined on domain described with guard
|
116
|
+
# and perform pattern extraction.
|
117
|
+
#
|
118
|
+
# @param pattern [String] pattern to match against
|
119
|
+
# @param guards [<#===>] other guards against extracted pattern
|
120
|
+
# @yieldparam hash [{Symbol => any}]
|
121
|
+
# @return [Fear::PartialFunction]
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# pf = Fear.xcase('['ok', Some(body)]') { |body:| ... }
|
125
|
+
# pf.defined_at?(['ok', Fear.some(body)]) #=> true
|
126
|
+
# pf.defined_at?(['err', Fear.none]) #=> false
|
127
|
+
#
|
128
|
+
# @example pattern and guards. It matches against non-empty body
|
129
|
+
#
|
130
|
+
# pf = Fear.xcase('['ok', Some(body)]', ->(body:) { !body.empty? }) { }
|
131
|
+
#
|
132
|
+
def xcase(pattern, *guards, &function)
|
133
|
+
warn "NOTE: Fear.xcase is deprecated and will be removed in a future version. Use `case .. in ..` instead."
|
134
|
+
Fear[pattern].and_then(self.case(*guards, &function))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|