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,129 @@
1
+ # PEG grammar for pattern matching and extracting
2
+
3
+ module Fear::Extractor
4
+ grammar Grammar
5
+ rule matcher
6
+ identified_matcher / anonymous_matcher
7
+ end
8
+
9
+ rule identified_matcher
10
+ identifier space '@' space anonymous_matcher <IdentifiedMatcher>
11
+ end
12
+
13
+ rule anonymous_matcher
14
+ array / literal / identifier / extractor / type
15
+ end
16
+
17
+ rule array
18
+ '[' (non_empty_array / array_splat / empty_array) ']' <ArrayLiteral>
19
+ end
20
+
21
+ rule empty_array
22
+ '' <EmptyArray>
23
+ end
24
+
25
+ rule non_empty_array
26
+ array_head (array_tail / empty_array) <NonEmptyArray>
27
+ end
28
+
29
+ rule array_tail
30
+ ',' (non_empty_array / array_splat) <ArrayTail>
31
+ end
32
+
33
+ rule array_splat
34
+ space? (named_array_splat / anonymous_array_splat) space? <ArraySplat>
35
+ end
36
+
37
+ rule array_splat_identifier
38
+ space? anonymous_array_splat space? <ArraySplat>
39
+ end
40
+
41
+ rule anonymous_array_splat
42
+ '*' '_'? <AnonymousArraySplat>
43
+ end
44
+
45
+ rule named_array_splat
46
+ '*' [a-z] [a-z0-9_]* <NamedArraySplat>
47
+ end
48
+
49
+ rule array_head
50
+ space? anonymous_matcher space? <ArrayHead>
51
+ end
52
+
53
+ rule space
54
+ [\s]+
55
+ end
56
+
57
+ rule any
58
+ '_' <AnyIdentifier>
59
+ end
60
+
61
+ rule variable_identifier
62
+ [a-z_] [a-z0-9_]* <Identifier>
63
+ end
64
+
65
+ rule identifier
66
+ typed_identifier / any / variable_identifier
67
+ end
68
+
69
+ rule typed_identifier
70
+ (any / variable_identifier) ' : ' type <TypedIdentifier>
71
+ end
72
+
73
+ rule literal
74
+ nil / true / false / number / string / symbol
75
+ end
76
+
77
+ rule number
78
+ float / integer
79
+ end
80
+
81
+ rule integer
82
+ ('+' / '-')? [0-9]+ <IntegerLiteral>
83
+ end
84
+
85
+ rule float
86
+ ('+' / '-')? [0-9]+ '.' [0-9]+ <FloatLiteral>
87
+ end
88
+
89
+ rule symbol
90
+ ':' (string / any / variable_identifier) <SymbolLiteral>
91
+ end
92
+
93
+ rule string
94
+ double_quoted_string / single_quoted_string
95
+ end
96
+
97
+ rule double_quoted_string
98
+ '"' ([^"\\] / '\\' . )* '"' <DoubleQuotedStringLiteral>
99
+ end
100
+
101
+ rule single_quoted_string
102
+ "'" ([^'\\] / '\\' . )* "'" <StringLiteral>
103
+ end
104
+
105
+ rule true
106
+ 'true' <TrueLiteral>
107
+ end
108
+
109
+ rule false
110
+ 'false' <FalseLiteral>
111
+ end
112
+
113
+ rule nil
114
+ 'nil' <NilLiteral>
115
+ end
116
+
117
+ rule extractor
118
+ type '(' non_empty_array? ')' <ExtractorLiteral>
119
+ end
120
+
121
+ rule type
122
+ constant ('::' constant)* <TypeLiteral>
123
+ end
124
+
125
+ rule constant
126
+ [A-Z] [a-zA-Z0-9_]*
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,16 @@
1
+ module Fear
2
+ module Extractor
3
+ class IdentifierMatcher < Matcher
4
+ # @!attribute name
5
+ # @return [Types::Strict::Symbol]
6
+
7
+ def defined_at?(_)
8
+ true
9
+ end
10
+
11
+ def bindings(other)
12
+ { name => other }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,54 @@
1
+ require 'ostruct'
2
+
3
+ module Fear
4
+ module Extractor
5
+ # @abstract abstract matcher to inherit from.
6
+ class Matcher < OpenStruct
7
+ autoload :And, 'fear/extractor/matcher/and'
8
+
9
+ EMPTY_HASH = {}.freeze
10
+ EMPTY_ARRAY = [].freeze
11
+
12
+ # @param node [Fear::Extractor::Grammar::Node]
13
+ def initialize(node:, **attributes)
14
+ @input = node.input
15
+ @input_position = node.interval.first
16
+ super(attributes)
17
+ end
18
+ attr_reader :input_position, :input
19
+ private :input
20
+ private :input_position
21
+
22
+ def call(arg)
23
+ call_or_else(arg, &PartialFunction::EMPTY)
24
+ end
25
+
26
+ def and(other)
27
+ And.new(self, other)
28
+ end
29
+
30
+ # @param arg [any]
31
+ # @yield [arg] if function not defined
32
+ def call_or_else(arg)
33
+ if defined_at?(arg)
34
+ bindings(arg)
35
+ else
36
+ yield arg
37
+ end
38
+ end
39
+
40
+ # Shows why matcher has failed. Use it for debugging.
41
+ # @example
42
+ # Fear['[1, 2, _]'].failure_reason([1, 3, 4])
43
+ # # it will show that the second element hasn't match
44
+ #
45
+ def failure_reason(other)
46
+ if defined_at?(other)
47
+ Fear.none
48
+ else
49
+ Fear.some("Expected `#{other.inspect}` to match:\n#{input}\n#{'~' * input_position}^")
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,36 @@
1
+ require 'ostruct'
2
+
3
+ module Fear
4
+ module Extractor
5
+ class Matcher < OpenStruct
6
+ # Combine two matchers, so both should pass
7
+ class And < Matcher
8
+ def initialize(matcher1, matcher2)
9
+ @matcher1 = matcher1
10
+ @matcher2 = matcher2
11
+ end
12
+ attr_reader :matcher1, :matcher2
13
+
14
+ def defined_at?(arg)
15
+ matcher1.defined_at?(arg) && matcher2.defined_at?(arg)
16
+ end
17
+
18
+ def bindings(arg)
19
+ matcher1.bindings(arg).merge(matcher2.bindings(arg))
20
+ end
21
+
22
+ def failure_reason(arg)
23
+ if matcher1.defined_at?(arg)
24
+ if matcher2.defined_at?(arg)
25
+ Fear.none
26
+ else
27
+ matcher2.failure_reason(arg)
28
+ end
29
+ else
30
+ matcher1.failure_reason(arg)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ module Fear
2
+ module Extractor
3
+ # Match against array splat, and capture rest of an array
4
+ # E.g. +[1, 2, *tail]+
5
+ #
6
+ class NamedArraySplatMatcher < ArraySplatMatcher
7
+ # @!attribute name
8
+ # @return [Types::Strict::Symbol]
9
+
10
+ def bindings(other)
11
+ { name => other }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ require 'lru_redux'
2
+
3
+ module Fear
4
+ module Extractor
5
+ # Parse pattern. Used within +Fear[]+
6
+ class Pattern
7
+ DEFAULT_PATTERN_CACHE_SIZE = 10_000
8
+ @pattern_cache = LruRedux::Cache.new(ENV.fetch('FEAR_PATTERNS_CACHE_SIZE', DEFAULT_PATTERN_CACHE_SIZE))
9
+
10
+ class << self
11
+ attr_reader :pattern_cache
12
+ end
13
+
14
+ def initialize(pattern)
15
+ @matcher = compile_pattern(pattern)
16
+ end
17
+ attr_reader :matcher
18
+ private :matcher
19
+
20
+ private def compile_pattern(pattern)
21
+ self.class.pattern_cache.getset(pattern) do
22
+ compile_pattern_without_cache(pattern)
23
+ end
24
+ end
25
+
26
+ private def compile_pattern_without_cache(pattern)
27
+ parser = Extractor::GrammarParser.new
28
+ if (result = parser.parse(pattern))
29
+ result.to_matcher
30
+ else
31
+ raise PatternSyntaxError, syntax_error_message(parser, pattern)
32
+ end
33
+ end
34
+
35
+ def ===(other)
36
+ matcher.defined_at?(other)
37
+ end
38
+
39
+ def and_then(other)
40
+ Fear::PartialFunction::Combined.new(matcher, other)
41
+ end
42
+
43
+ def failure_reason(other)
44
+ matcher.failure_reason(other).get_or_else { 'It matches' }
45
+ end
46
+
47
+ private def syntax_error_message(parser, pattern)
48
+ parser.failure_reason =~ /^(Expected .+) after/m
49
+ "#{Regexp.last_match(1).gsub("\n", '$NEWLINE')}:\n" +
50
+ pattern.split("\n")[parser.failure_line - 1] + "\n" \
51
+ "#{'~' * (parser.failure_column - 1)}^\n"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,24 @@
1
+ module Fear
2
+ module Extractor
3
+ # Match and capture identifier with specific type. E.g. +foo : Integer+
4
+ #
5
+ class TypedIdentifierMatcher < Matcher
6
+ # @!attribute identifier
7
+ # @return [IdentifierMatcher]
8
+ # @!attribute type
9
+ # @return [ValueMatcher]
10
+
11
+ def defined_at?(other)
12
+ type.defined_at?(other)
13
+ end
14
+
15
+ def bindings(other)
16
+ { identifier.name => other }
17
+ end
18
+
19
+ def failure_reason(other)
20
+ type.failure_reason(other)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Fear
2
+ module Extractor
3
+ # Match against values -- true, false, 1, "foo" etc.
4
+ class ValueMatcher < Matcher
5
+ # @!attribute value
6
+ # @return [Any]
7
+
8
+ def defined_at?(arg)
9
+ value === arg
10
+ end
11
+
12
+ def bindings(_)
13
+ EMPTY_HASH
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module Fear
2
+ module ExtractorApi
3
+ # Allows to pattern match and extract matcher variables
4
+ #
5
+ # @param pattern [String]
6
+ # @return [Extractor::Pattern]
7
+ # @note it is not intended to be used by itself, rather then with partial functions
8
+ def [](pattern)
9
+ Extractor::Pattern.new(pattern)
10
+ end
11
+
12
+ # Register extractor for given class
13
+ # @!method register_extractor(*names, extractor)
14
+ # @param names [<Class, String>, Class, String] name of a class. You can also pass alias for the name
15
+ # @param extractor [Proc<any => Fear::Option>] proc taking any argument and returned Option
16
+ # of extracted value('s)
17
+ #
18
+ # @example
19
+ # register_extractor(Fear::Some, Fear.case(Fear::Some) { |some| some.get }.lift)
20
+ #
21
+ # register_extractor(User, Fear.case(User) { |user|} [user.id, user.email] , )
22
+ #
23
+ # @example registering an alias. Alias should be CamelCased string
24
+ # register_extractor(Fear::Some, 'Some', Fear.case(Fear::Some) { |some| some.get }.lift)
25
+ #
26
+ # # no you can extract Fear::Some's using Some alias
27
+ # m.case(Fear['Some(value : Integer)']) { |value:| value * 2 }
28
+ #
29
+ def register_extractor(*args)
30
+ Extractor.register_extractor(*args)
31
+ end
32
+ end
33
+ end
@@ -1,9 +1,15 @@
1
1
  module Fear
2
2
  class Failure
3
3
  include Try
4
- include Dry::Equalizer(:exception)
5
4
  include RightBiased::Left
6
5
  include FailurePatternMatch.mixin
6
+ EXTRACTOR = proc do |try|
7
+ if Fear::Failure === try
8
+ Fear.some([try.exception])
9
+ else
10
+ Fear.none
11
+ end
12
+ end
7
13
 
8
14
  # @param [StandardError]
9
15
  def initialize(exception)
@@ -44,22 +50,24 @@ module Fear
44
50
  self
45
51
  end
46
52
 
47
- # @yieldparam [Exception]
48
- # @yieldreturn [Try]
49
- # @return [Try]
53
+ # @yieldparam [Fear::PatternMatch]
54
+ # @yieldreturn [Fear::Try]
55
+ # @return [Fear::Try]
50
56
  def recover_with
51
- yield(exception).tap do |result|
52
- Utils.assert_type!(result, Success, Failure)
53
- end
57
+ Fear.matcher { |m| yield(m) }
58
+ .and_then { |result| result.tap { Utils.assert_type!(result, Success, Failure) } }
59
+ .call_or_else(exception) { self }
54
60
  rescue StandardError => error
55
61
  Failure.new(error)
56
62
  end
57
63
 
58
- # @yieldparam [Exception]
64
+ # @yieldparam [Fear::PatternMatch]
59
65
  # @yieldreturn [any]
60
- # @return [Try]
66
+ # @return [Fear::Try]
61
67
  def recover
62
- Success.new(yield(exception))
68
+ Fear.matcher { |m| yield(m) }
69
+ .and_then { |v| Success.new(v) }
70
+ .call_or_else(exception) { self }
63
71
  rescue StandardError => error
64
72
  Failure.new(error)
65
73
  end
@@ -69,6 +77,12 @@ module Fear
69
77
  Left.new(exception)
70
78
  end
71
79
 
80
+ # @param other [Any]
81
+ # @return [Boolean]
82
+ def ==(other)
83
+ other.is_a?(Failure) && exception == other.exception
84
+ end
85
+
72
86
  # Used in case statement
73
87
  # @param other [any]
74
88
  # @return [Boolean]
@@ -79,5 +93,13 @@ module Fear
79
93
  super
80
94
  end
81
95
  end
96
+
97
+ # @return [String]
98
+ def inspect
99
+ "#<Fear::Failure exception=#{exception.inspect}>"
100
+ end
101
+
102
+ # @return [String]
103
+ alias to_s inspect
82
104
  end
83
105
  end