fear 0.11.0 → 1.0.0

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