fear 0.11.0 → 1.0.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 (110) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -1
  3. data/.rubocop.yml +18 -0
  4. data/.travis.yml +0 -3
  5. data/CHANGELOG.md +12 -1
  6. data/Gemfile +1 -0
  7. data/{gemfiles/dry_equalizer_0.2.1.gemfile.lock → Gemfile.lock} +21 -12
  8. data/README.md +594 -241
  9. data/Rakefile +166 -219
  10. data/benchmarks/README.md +1 -0
  11. data/benchmarks/dry_do_vs_fear_for.txt +11 -0
  12. data/benchmarks/dry_some_fmap_vs_fear_some_map.txt +11 -0
  13. data/benchmarks/factorial.txt +16 -0
  14. data/benchmarks/fear_gaurd_and1_vs_new.txt +13 -0
  15. data/benchmarks/fear_gaurd_and2_vs_and.txt +13 -0
  16. data/benchmarks/fear_gaurd_and3_vs_and_and.txt +13 -0
  17. data/benchmarks/fear_pattern_extracting_with_vs_without_cache.txt +11 -0
  18. data/benchmarks/fear_pattern_matching_construction_vs_execution.txt +13 -0
  19. data/benchmarks/pattern_matching_dry_vs_qo_vs_fear_try.txt +14 -0
  20. data/benchmarks/pattern_matching_qo_vs_fear_pattern_extraction.txt +11 -0
  21. data/benchmarks/pattern_matching_qo_vs_fear_try_execution.txt +11 -0
  22. data/examples/pattern_extracting.rb +15 -0
  23. data/examples/pattern_matching_binary_tree_set.rb +96 -0
  24. data/examples/pattern_matching_number_in_words.rb +54 -0
  25. data/fear.gemspec +4 -2
  26. data/lib/fear.rb +21 -4
  27. data/lib/fear/either.rb +77 -59
  28. data/lib/fear/either_api.rb +21 -0
  29. data/lib/fear/empty_partial_function.rb +1 -1
  30. data/lib/fear/extractor.rb +108 -0
  31. data/lib/fear/extractor/anonymous_array_splat_matcher.rb +8 -0
  32. data/lib/fear/extractor/any_matcher.rb +15 -0
  33. data/lib/fear/extractor/array_head_matcher.rb +34 -0
  34. data/lib/fear/extractor/array_matcher.rb +38 -0
  35. data/lib/fear/extractor/array_splat_matcher.rb +14 -0
  36. data/lib/fear/extractor/empty_list_matcher.rb +18 -0
  37. data/lib/fear/extractor/extractor_matcher.rb +42 -0
  38. data/lib/fear/extractor/grammar.rb +201 -0
  39. data/lib/fear/extractor/grammar.treetop +129 -0
  40. data/lib/fear/extractor/identifier_matcher.rb +16 -0
  41. data/lib/fear/extractor/matcher.rb +54 -0
  42. data/lib/fear/extractor/matcher/and.rb +36 -0
  43. data/lib/fear/extractor/named_array_splat_matcher.rb +15 -0
  44. data/lib/fear/extractor/pattern.rb +55 -0
  45. data/lib/fear/extractor/typed_identifier_matcher.rb +24 -0
  46. data/lib/fear/extractor/value_matcher.rb +17 -0
  47. data/lib/fear/extractor_api.rb +33 -0
  48. data/lib/fear/failure.rb +32 -10
  49. data/lib/fear/for.rb +14 -69
  50. data/lib/fear/for_api.rb +66 -0
  51. data/lib/fear/future.rb +414 -0
  52. data/lib/fear/future_api.rb +19 -0
  53. data/lib/fear/left.rb +8 -0
  54. data/lib/fear/none.rb +17 -8
  55. data/lib/fear/option.rb +55 -49
  56. data/lib/fear/option_api.rb +38 -0
  57. data/lib/fear/partial_function.rb +9 -12
  58. data/lib/fear/partial_function/empty.rb +1 -1
  59. data/lib/fear/partial_function/guard.rb +8 -20
  60. data/lib/fear/partial_function/lifted.rb +1 -0
  61. data/lib/fear/partial_function_class.rb +10 -0
  62. data/lib/fear/pattern_match.rb +10 -0
  63. data/lib/fear/pattern_matching_api.rb +35 -11
  64. data/lib/fear/promise.rb +87 -0
  65. data/lib/fear/right.rb +8 -0
  66. data/lib/fear/some.rb +22 -3
  67. data/lib/fear/success.rb +22 -1
  68. data/lib/fear/try.rb +82 -67
  69. data/lib/fear/try_api.rb +31 -0
  70. data/lib/fear/unit.rb +28 -0
  71. data/lib/fear/version.rb +1 -1
  72. data/spec/fear/done_spec.rb +3 -3
  73. data/spec/fear/either/mixin_spec.rb +15 -0
  74. data/spec/fear/either_pattern_match_spec.rb +10 -12
  75. data/spec/fear/extractor/array_matcher_spec.rb +228 -0
  76. data/spec/fear/extractor/extractor_matcher_spec.rb +151 -0
  77. data/spec/fear/extractor/grammar_array_spec.rb +23 -0
  78. data/spec/fear/extractor/identified_matcher_spec.rb +47 -0
  79. data/spec/fear/extractor/identifier_matcher_spec.rb +66 -0
  80. data/spec/fear/extractor/pattern_spec.rb +32 -0
  81. data/spec/fear/extractor/typed_identifier_matcher_spec.rb +62 -0
  82. data/spec/fear/extractor/value_matcher_number_spec.rb +77 -0
  83. data/spec/fear/extractor/value_matcher_string_spec.rb +86 -0
  84. data/spec/fear/extractor/value_matcher_symbol_spec.rb +69 -0
  85. data/spec/fear/extractor_api_spec.rb +113 -0
  86. data/spec/fear/extractor_spec.rb +59 -0
  87. data/spec/fear/failure_spec.rb +73 -13
  88. data/spec/fear/for_spec.rb +35 -35
  89. data/spec/fear/future_spec.rb +466 -0
  90. data/spec/fear/guard_spec.rb +4 -4
  91. data/spec/fear/left_spec.rb +40 -14
  92. data/spec/fear/none_spec.rb +28 -12
  93. data/spec/fear/option/mixin_spec.rb +37 -0
  94. data/spec/fear/option_pattern_match_spec.rb +7 -9
  95. data/spec/fear/partial_function_spec.rb +25 -3
  96. data/spec/fear/pattern_match_spec.rb +33 -1
  97. data/spec/fear/promise_spec.rb +94 -0
  98. data/spec/fear/right_spec.rb +37 -9
  99. data/spec/fear/some_spec.rb +32 -6
  100. data/spec/fear/success_spec.rb +32 -4
  101. data/spec/fear/try/mixin_spec.rb +17 -0
  102. data/spec/fear/try_pattern_match_spec.rb +8 -10
  103. data/spec/spec_helper.rb +1 -1
  104. metadata +115 -20
  105. data/Appraisals +0 -32
  106. data/gemfiles/dry_equalizer_0.1.0.gemfile +0 -8
  107. data/gemfiles/dry_equalizer_0.1.0.gemfile.lock +0 -82
  108. data/gemfiles/dry_equalizer_0.2.1.gemfile +0 -8
  109. data/lib/fear/done.rb +0 -22
  110. data/spec/fear/option_spec.rb +0 -15
@@ -0,0 +1,21 @@
1
+ module Fear
2
+ module EitherApi
3
+ # @param value [any]
4
+ # @return [Fear::Left]
5
+ # @example
6
+ # Fear.left(42) #=> #<Fear::Left value=42>
7
+ #
8
+ def left(value)
9
+ Fear::Left.new(value)
10
+ end
11
+
12
+ # @param value [any]
13
+ # @return [Fear::Right]
14
+ # @example
15
+ # Fear.right(42) #=> #<Fear::Right value=42>
16
+ #
17
+ def right(value)
18
+ Fear::Right.new(value)
19
+ end
20
+ end
21
+ end
@@ -1,7 +1,7 @@
1
1
  module Fear
2
2
  # Use singleton version of EmptyPartialFunction -- PartialFunction::EMPTY
3
3
  # @api private
4
- module EmptyPartialFunction
4
+ class EmptyPartialFunction
5
5
  include PartialFunction
6
6
 
7
7
  def defined_at?(_)
@@ -0,0 +1,108 @@
1
+ require 'treetop'
2
+ require 'fear/extractor/grammar'
3
+ Treetop.load File.expand_path('extractor/grammar.treetop', __dir__)
4
+
5
+ module Fear
6
+ # @api private
7
+ module Extractor
8
+ autoload :Pattern, 'fear/extractor/pattern'
9
+ autoload :Matcher, 'fear/extractor/matcher'
10
+
11
+ autoload :AnonymousArraySplatMatcher, 'fear/extractor/anonymous_array_splat_matcher'
12
+ autoload :AnyMatcher, 'fear/extractor/any_matcher'
13
+ autoload :ArrayHeadMatcher, 'fear/extractor/array_head_matcher'
14
+ autoload :ArrayMatcher, 'fear/extractor/array_matcher'
15
+ autoload :ArraySplatMatcher, 'fear/extractor/array_splat_matcher'
16
+ autoload :EmptyListMatcher, 'fear/extractor/empty_list_matcher'
17
+ autoload :ExtractorMatcher, 'fear/extractor/extractor_matcher'
18
+ autoload :IdentifierMatcher, 'fear/extractor/identifier_matcher'
19
+ autoload :NamedArraySplatMatcher, 'fear/extractor/named_array_splat_matcher'
20
+ autoload :TypedIdentifierMatcher, 'fear/extractor/typed_identifier_matcher'
21
+ autoload :ValueMatcher, 'fear/extractor/value_matcher'
22
+
23
+ ExtractorNotFound = Class.new(Error)
24
+
25
+ @mutex = Mutex.new
26
+ @registry = PartialFunction::EMPTY
27
+
28
+ EXTRACTOR_NOT_FOUND = proc do |klass|
29
+ raise ExtractorNotFound, 'could not find extractor for ' + klass.inspect
30
+ end
31
+
32
+ class << self
33
+ # @param klass [Class, String]
34
+ # @api private
35
+ def find_extractor(klass)
36
+ @registry.call_or_else(klass, &EXTRACTOR_NOT_FOUND)
37
+ end
38
+
39
+ # Register extractor for given class
40
+ # @!method register_extractor(*names, extractor)
41
+ # @param names [<Class, String>, Class, String] name of a class. You can also pass alias for the name
42
+ # @param extractor [Proc<any => Fear::Option>] proc taking any argument and returned Option
43
+ # of extracted value('s)
44
+ #
45
+ # @example
46
+ # register_extractor(Fear::Some, Fear.case(Fear::Some) { |some| some.get }.lift)
47
+ #
48
+ # register_extractor(User, Fear.case(User) { |user|} [user.id, user.email] , )
49
+ #
50
+ # @example registering an alias. Alias should be CamelCased string
51
+ # register_extractor(Fear::Some, 'Some', Fear.case(Fear::Some) { |some| some.get }.lift)
52
+ #
53
+ # # no you can extract Fear::Some's using Some alias
54
+ # m.case(Fear['Some(value : Integer)']) { |value:| value * 2 }
55
+ #
56
+ def register_extractor(*args)
57
+ *keys, extractor = *args
58
+
59
+ @mutex.synchronize do
60
+ keys.uniq.each do |key|
61
+ @registry = BUILD_EXTRACTOR.call(key, extractor).or_else(@registry)
62
+ end
63
+ end
64
+ self
65
+ end
66
+
67
+ BUILD_EXTRACTOR = proc do |key, extractor|
68
+ Fear.matcher do |m|
69
+ case key
70
+ when String
71
+ m.case(Module, ->(lookup) { lookup.to_s == key }) { extractor }
72
+ m.case(String, key) { extractor }
73
+ when Module
74
+ m.case(Module, ->(lookup) { lookup <= key }) { extractor }
75
+ m.case(String, key.to_s) { extractor }
76
+ else
77
+ m.case(key) { extractor } # may it be useful to register other types of keys? lambda?
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ # Multiple arguments extractor example
84
+ register_extractor('Date', proc do |other|
85
+ if other.class.name == 'Date'
86
+ Fear.some([other.year, other.month, other.day])
87
+ else
88
+ Fear.none
89
+ end
90
+ end)
91
+ register_extractor(Struct, Fear.case(Struct, &:to_a).lift)
92
+ # No argument boolean extractor example
93
+ register_extractor('IsEven', proc do |int|
94
+ if int.is_a?(Integer) && int.even?
95
+ Fear.some([])
96
+ else
97
+ Fear.none
98
+ end
99
+ end)
100
+ # Single argument extractor example
101
+ register_extractor('Fear::Some', 'Some', Some::EXTRACTOR)
102
+ register_extractor('Fear::None', 'None', NoneClass::EXTRACTOR)
103
+ register_extractor('Fear::Right', 'Right', Right::EXTRACTOR)
104
+ register_extractor('Fear::Left', 'Left', Left::EXTRACTOR)
105
+ register_extractor('Fear::Success', 'Success', Success::EXTRACTOR)
106
+ register_extractor('Fear::Failure', 'Failure', Failure::EXTRACTOR)
107
+ end
108
+ end
@@ -0,0 +1,8 @@
1
+ module Fear
2
+ module Extractor
3
+ # Match against array splat, E.g. `[1, 2, *]` or `[1, 2, *_]`
4
+ #
5
+ class AnonymousArraySplatMatcher < ArraySplatMatcher
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ module Fear
2
+ module Extractor
3
+ # Always match, E.g. `_ : Integer` without capturing variable
4
+ #
5
+ class AnyMatcher < Matcher
6
+ def defined_at?(_other)
7
+ true
8
+ end
9
+
10
+ def bindings(_)
11
+ EMPTY_HASH
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ module Fear
2
+ module Extractor
3
+ # Part of recursive array matcher. Match against its head.
4
+ # @see ArrayMatcher
5
+ class ArrayHeadMatcher < Matcher
6
+ # @!attribute matcher
7
+ # @return [Matcher]
8
+ # @!attribute index
9
+ # @return [Types::Strict::Integer]
10
+
11
+ # @param other [<>]
12
+ def defined_at?(other)
13
+ if other.empty?
14
+ false
15
+ else
16
+ matcher.defined_at?(other.first)
17
+ end
18
+ end
19
+
20
+ # @param other [<>]
21
+ def bindings(other)
22
+ if other.empty?
23
+ super
24
+ else
25
+ matcher.bindings(other.first)
26
+ end
27
+ end
28
+
29
+ def failure_reason(other)
30
+ matcher.failure_reason(other.first)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ module Fear
2
+ module Extractor
3
+ # Recursive array matcher. Match against its head and tail
4
+ #
5
+ class ArrayMatcher < Matcher
6
+ # @!attribute head
7
+ # @return [ArrayHeadMatcher]
8
+ # @!attribute tail
9
+ # @return [ArrayMatcher | EmptyListMatcher]
10
+
11
+ def defined_at?(other)
12
+ if other.is_a?(Array)
13
+ head.defined_at?(other) && tail.defined_at?(other.slice(1..-1))
14
+ end
15
+ end
16
+
17
+ def bindings(other)
18
+ if head.is_a?(ArraySplatMatcher)
19
+ head.bindings(other)
20
+ else
21
+ head.bindings(other).merge(tail.bindings(other.slice(1..-1)))
22
+ end
23
+ end
24
+
25
+ def failure_reason(other)
26
+ if other.is_a?(Array)
27
+ if head.defined_at?(other)
28
+ tail.failure_reason(other.slice(1..-1))
29
+ else
30
+ head.failure_reason(other)
31
+ end
32
+ else
33
+ super
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ module Fear
2
+ module Extractor
3
+ # @abstract
4
+ class ArraySplatMatcher < Matcher
5
+ def defined_at?(_other)
6
+ true
7
+ end
8
+
9
+ def bindings(_)
10
+ EMPTY_HASH
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module Fear
2
+ module Extractor
3
+ # Match only if array is empty
4
+ #
5
+ class EmptyListMatcher < Matcher
6
+ # @!attribute index
7
+ # @return [Types::Strict::Integer]
8
+ #
9
+ def defined_at?(other)
10
+ other.empty?
11
+ end
12
+
13
+ def bindings(_)
14
+ EMPTY_HASH
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ module Fear
2
+ module Extractor
3
+ # Match and extract pattern using registered extractor objects
4
+ # E.g. +Some(a : Integer)+
5
+ # @see Extractor.register_extractor
6
+ class ExtractorMatcher < Matcher
7
+ # @!attribute name
8
+ # @return [Types::Strict::String]
9
+ # @!attribute arguments_matcher
10
+ # @return [ArrayMatcher | EmptyListMatcher]
11
+ #
12
+
13
+ def initialize(*)
14
+ super
15
+ @extractor = Extractor.find_extractor(name)
16
+ end
17
+ attr_reader :extractor
18
+ private :extractor
19
+
20
+ def defined_at?(other)
21
+ extractor
22
+ .call(other)
23
+ .map { |v| arguments_matcher.defined_at?(v) }
24
+ .get_or_else(false)
25
+ end
26
+
27
+ def call_or_else(arg)
28
+ extractor.call(arg)
29
+ .map { |v| arguments_matcher.call_or_else(v) { yield arg } }
30
+ .get_or_else { yield arg }
31
+ end
32
+
33
+ def failure_reason(other)
34
+ extractor.call(other).match do |m|
35
+ m.some(->(v) { arguments_matcher.defined_at?(v) }) { Fear.none }
36
+ m.some { |v| arguments_matcher.failure_reason(v) }
37
+ m.none { super }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,201 @@
1
+ module Fear
2
+ module Extractor
3
+ # This module contains AST nodes for GrammarParser
4
+ # generated by +treetop+. The sole purpose of them all is to
5
+ # generate matchers
6
+ module Grammar
7
+ class Node < Treetop::Runtime::SyntaxNode
8
+ end
9
+
10
+ class EmptyArray < Node
11
+ def to_matcher
12
+ EmptyListMatcher.new(node: self)
13
+ end
14
+ end
15
+
16
+ class ArrayLiteral < Node
17
+ def to_matcher
18
+ elements[1].to_matcher
19
+ end
20
+ end
21
+
22
+ class NonEmptyArray < Node
23
+ def to_matcher
24
+ head, tail = elements
25
+ ArrayMatcher.new(head: head.to_matcher, tail: tail.to_matcher, node: self)
26
+ end
27
+ end
28
+
29
+ class ArrayTail < Node
30
+ def to_matcher
31
+ elements[1].to_matcher
32
+ end
33
+ end
34
+
35
+ class ArrayHead < Node
36
+ def to_matcher
37
+ ArrayHeadMatcher.new(matcher: elements[1].to_matcher, node: elements[1])
38
+ end
39
+ end
40
+
41
+ class ArraySplat < Node
42
+ def to_matcher
43
+ elements[1].to_matcher
44
+ end
45
+ end
46
+
47
+ class AnonymousArraySplat < Node
48
+ def to_matcher
49
+ AnonymousArraySplatMatcher.new(node: self)
50
+ end
51
+ end
52
+
53
+ class FloatLiteral < Node
54
+ def to_matcher
55
+ ValueMatcher.new(value: value, node: self)
56
+ end
57
+
58
+ def value
59
+ text_value.to_f
60
+ end
61
+ end
62
+
63
+ class IntegerLiteral < Node
64
+ def to_matcher
65
+ ValueMatcher.new(value: value, node: self)
66
+ end
67
+
68
+ def value
69
+ text_value.to_i
70
+ end
71
+ end
72
+
73
+ class StringLiteral < Node
74
+ def to_matcher
75
+ ValueMatcher.new(value: value, node: self)
76
+ end
77
+
78
+ def value
79
+ elements[1].text_value
80
+ end
81
+ end
82
+
83
+ require 'yaml'
84
+
85
+ class DoubleQuotedStringLiteral < StringLiteral
86
+ def to_matcher
87
+ ValueMatcher.new(value: value, node: self)
88
+ end
89
+
90
+ def value
91
+ YAML.safe_load(%(---\n"#{super}"\n))
92
+ end
93
+ end
94
+
95
+ class SymbolLiteral < Node
96
+ def to_matcher
97
+ ValueMatcher.new(value: value, node: self)
98
+ end
99
+
100
+ def value
101
+ elements[1].value.to_sym
102
+ end
103
+ end
104
+
105
+ class TrueLiteral < Node
106
+ def to_matcher
107
+ ValueMatcher.new(value: true, node: self)
108
+ end
109
+ end
110
+
111
+ class FalseLiteral < Node
112
+ def to_matcher
113
+ ValueMatcher.new(value: false, node: self)
114
+ end
115
+ end
116
+
117
+ class NilLiteral < Node
118
+ def to_matcher
119
+ ValueMatcher.new(value: nil, node: self)
120
+ end
121
+ end
122
+
123
+ class AnyIdentifier < Node
124
+ def to_matcher
125
+ AnyMatcher.new(node: self)
126
+ end
127
+ end
128
+
129
+ class Identifier < Node
130
+ def to_matcher
131
+ IdentifierMatcher.new(name: value, node: self)
132
+ end
133
+
134
+ def value
135
+ text_value.to_sym
136
+ end
137
+ end
138
+
139
+ class NamedArraySplat < Node
140
+ def to_matcher
141
+ NamedArraySplatMatcher.new(name: name, node: self)
142
+ end
143
+
144
+ def name
145
+ text_value[1..-1].to_sym
146
+ end
147
+ end
148
+
149
+ class TypeLiteral < Node
150
+ def to_matcher
151
+ ValueMatcher.new(value: value, node: self)
152
+ end
153
+
154
+ def value
155
+ Object.const_get(text_value)
156
+ end
157
+ end
158
+
159
+ class TypedIdentifier < Node
160
+ def to_matcher
161
+ identifier, type = elements.values_at(0, 2)
162
+ type.to_matcher.and(identifier.to_matcher)
163
+ end
164
+ end
165
+
166
+ class IdentifiedMatcher < Node
167
+ def to_matcher
168
+ identifier, matcher = elements.values_at(0, -1)
169
+ identifier.to_matcher.and(matcher.to_matcher)
170
+ end
171
+ end
172
+
173
+ class ExtractorLiteral < Node
174
+ def to_matcher
175
+ ExtractorMatcher.new(
176
+ name: extractor_name,
177
+ arguments_matcher: extractor_arguments,
178
+ node: self,
179
+ )
180
+ end
181
+
182
+ private def extractor_name
183
+ name = elements[0].text_value
184
+ if Object.const_defined?(name)
185
+ Object.const_get(name)
186
+ else
187
+ name
188
+ end
189
+ end
190
+
191
+ private def extractor_arguments
192
+ if elements[2].empty?
193
+ EmptyListMatcher.new(node: self)
194
+ else
195
+ elements[2].to_matcher
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end