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.
Files changed (155) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/rubocop.yml +39 -0
  3. data/.github/workflows/spec.yml +42 -0
  4. data/.gitignore +0 -1
  5. data/.rubocop.yml +4 -12
  6. data/.simplecov +17 -0
  7. data/CHANGELOG.md +40 -0
  8. data/Gemfile +5 -2
  9. data/Gemfile.lock +130 -0
  10. data/LICENSE.txt +1 -1
  11. data/README.md +1293 -97
  12. data/Rakefile +369 -1
  13. data/benchmarks/README.md +1 -0
  14. data/benchmarks/dry_do_vs_fear_for.txt +11 -0
  15. data/benchmarks/dry_some_fmap_vs_fear_some_map.txt +11 -0
  16. data/benchmarks/factorial.txt +16 -0
  17. data/benchmarks/fear_gaurd_and1_vs_new.txt +13 -0
  18. data/benchmarks/fear_gaurd_and2_vs_and.txt +13 -0
  19. data/benchmarks/fear_gaurd_and3_vs_and_and.txt +13 -0
  20. data/benchmarks/fear_pattern_extracting_with_vs_without_cache.txt +11 -0
  21. data/benchmarks/fear_pattern_matching_construction_vs_execution.txt +13 -0
  22. data/benchmarks/pattern_matching_dry_vs_qo_vs_fear_try.txt +14 -0
  23. data/benchmarks/pattern_matching_qo_vs_fear_pattern_extraction.txt +11 -0
  24. data/benchmarks/pattern_matching_qo_vs_fear_try_execution.txt +11 -0
  25. data/examples/pattern_extracting.rb +17 -0
  26. data/examples/pattern_extracting_ruby2.7.rb +15 -0
  27. data/examples/pattern_matching_binary_tree_set.rb +101 -0
  28. data/examples/pattern_matching_number_in_words.rb +60 -0
  29. data/fear.gemspec +34 -23
  30. data/lib/dry/types/fear.rb +8 -0
  31. data/lib/dry/types/fear/option.rb +125 -0
  32. data/lib/fear.rb +65 -15
  33. data/lib/fear/await.rb +33 -0
  34. data/lib/fear/awaitable.rb +28 -0
  35. data/lib/fear/either.rb +131 -71
  36. data/lib/fear/either_api.rb +23 -0
  37. data/lib/fear/either_pattern_match.rb +53 -0
  38. data/lib/fear/empty_partial_function.rb +38 -0
  39. data/lib/fear/extractor.rb +112 -0
  40. data/lib/fear/extractor/anonymous_array_splat_matcher.rb +10 -0
  41. data/lib/fear/extractor/any_matcher.rb +17 -0
  42. data/lib/fear/extractor/array_head_matcher.rb +36 -0
  43. data/lib/fear/extractor/array_matcher.rb +40 -0
  44. data/lib/fear/extractor/array_splat_matcher.rb +16 -0
  45. data/lib/fear/extractor/empty_list_matcher.rb +20 -0
  46. data/lib/fear/extractor/extractor_matcher.rb +44 -0
  47. data/lib/fear/extractor/grammar.rb +203 -0
  48. data/lib/fear/extractor/grammar.treetop +129 -0
  49. data/lib/fear/extractor/identifier_matcher.rb +18 -0
  50. data/lib/fear/extractor/matcher.rb +53 -0
  51. data/lib/fear/extractor/matcher/and.rb +38 -0
  52. data/lib/fear/extractor/named_array_splat_matcher.rb +17 -0
  53. data/lib/fear/extractor/pattern.rb +58 -0
  54. data/lib/fear/extractor/typed_identifier_matcher.rb +26 -0
  55. data/lib/fear/extractor/value_matcher.rb +19 -0
  56. data/lib/fear/extractor_api.rb +35 -0
  57. data/lib/fear/failure.rb +46 -14
  58. data/lib/fear/failure_pattern_match.rb +10 -0
  59. data/lib/fear/for.rb +37 -95
  60. data/lib/fear/for_api.rb +68 -0
  61. data/lib/fear/future.rb +497 -0
  62. data/lib/fear/future_api.rb +21 -0
  63. data/lib/fear/left.rb +19 -2
  64. data/lib/fear/left_pattern_match.rb +11 -0
  65. data/lib/fear/none.rb +67 -3
  66. data/lib/fear/none_pattern_match.rb +14 -0
  67. data/lib/fear/option.rb +120 -56
  68. data/lib/fear/option_api.rb +40 -0
  69. data/lib/fear/option_pattern_match.rb +48 -0
  70. data/lib/fear/partial_function.rb +176 -0
  71. data/lib/fear/partial_function/and_then.rb +50 -0
  72. data/lib/fear/partial_function/any.rb +28 -0
  73. data/lib/fear/partial_function/combined.rb +53 -0
  74. data/lib/fear/partial_function/empty.rb +10 -0
  75. data/lib/fear/partial_function/guard.rb +80 -0
  76. data/lib/fear/partial_function/guard/and.rb +38 -0
  77. data/lib/fear/partial_function/guard/and3.rb +41 -0
  78. data/lib/fear/partial_function/guard/or.rb +38 -0
  79. data/lib/fear/partial_function/lifted.rb +23 -0
  80. data/lib/fear/partial_function/or_else.rb +64 -0
  81. data/lib/fear/partial_function_class.rb +38 -0
  82. data/lib/fear/pattern_match.rb +114 -0
  83. data/lib/fear/pattern_matching_api.rb +137 -0
  84. data/lib/fear/promise.rb +95 -0
  85. data/lib/fear/right.rb +20 -2
  86. data/lib/fear/right_biased.rb +6 -14
  87. data/lib/fear/right_pattern_match.rb +11 -0
  88. data/lib/fear/some.rb +55 -3
  89. data/lib/fear/some_pattern_match.rb +13 -0
  90. data/lib/fear/struct.rb +248 -0
  91. data/lib/fear/success.rb +35 -5
  92. data/lib/fear/success_pattern_match.rb +12 -0
  93. data/lib/fear/try.rb +136 -79
  94. data/lib/fear/try_api.rb +33 -0
  95. data/lib/fear/try_pattern_match.rb +33 -0
  96. data/lib/fear/unit.rb +32 -0
  97. data/lib/fear/utils.rb +39 -14
  98. data/lib/fear/version.rb +4 -1
  99. data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
  100. data/spec/dry/types/fear/option/core_spec.rb +77 -0
  101. data/spec/dry/types/fear/option/default_spec.rb +21 -0
  102. data/spec/dry/types/fear/option/hash_spec.rb +58 -0
  103. data/spec/dry/types/fear/option/option_spec.rb +97 -0
  104. data/spec/fear/awaitable_spec.rb +17 -0
  105. data/spec/fear/done_spec.rb +8 -6
  106. data/spec/fear/either/mixin_spec.rb +17 -0
  107. data/spec/fear/either_pattern_match_spec.rb +37 -0
  108. data/spec/fear/either_pattern_matching_spec.rb +28 -0
  109. data/spec/fear/extractor/array_matcher_spec.rb +230 -0
  110. data/spec/fear/extractor/extractor_matcher_spec.rb +153 -0
  111. data/spec/fear/extractor/grammar_array_spec.rb +25 -0
  112. data/spec/fear/extractor/identified_matcher_spec.rb +49 -0
  113. data/spec/fear/extractor/identifier_matcher_spec.rb +68 -0
  114. data/spec/fear/extractor/pattern_spec.rb +34 -0
  115. data/spec/fear/extractor/typed_identifier_matcher_spec.rb +64 -0
  116. data/spec/fear/extractor/value_matcher_number_spec.rb +79 -0
  117. data/spec/fear/extractor/value_matcher_string_spec.rb +88 -0
  118. data/spec/fear/extractor/value_matcher_symbol_spec.rb +71 -0
  119. data/spec/fear/extractor_api_spec.rb +115 -0
  120. data/spec/fear/extractor_spec.rb +61 -0
  121. data/spec/fear/failure_spec.rb +145 -45
  122. data/spec/fear/for_spec.rb +57 -67
  123. data/spec/fear/future_spec.rb +691 -0
  124. data/spec/fear/guard_spec.rb +103 -0
  125. data/spec/fear/left_spec.rb +112 -46
  126. data/spec/fear/none_spec.rb +114 -16
  127. data/spec/fear/option/mixin_spec.rb +39 -0
  128. data/spec/fear/option_pattern_match_spec.rb +35 -0
  129. data/spec/fear/option_pattern_matching_spec.rb +34 -0
  130. data/spec/fear/option_spec.rb +121 -8
  131. data/spec/fear/partial_function/empty_spec.rb +38 -0
  132. data/spec/fear/partial_function_and_then_spec.rb +147 -0
  133. data/spec/fear/partial_function_composition_spec.rb +82 -0
  134. data/spec/fear/partial_function_or_else_spec.rb +276 -0
  135. data/spec/fear/partial_function_spec.rb +239 -0
  136. data/spec/fear/pattern_match_spec.rb +93 -0
  137. data/spec/fear/pattern_matching_api_spec.rb +31 -0
  138. data/spec/fear/promise_spec.rb +96 -0
  139. data/spec/fear/right_biased/left.rb +29 -32
  140. data/spec/fear/right_biased/right.rb +51 -54
  141. data/spec/fear/right_spec.rb +109 -41
  142. data/spec/fear/some_spec.rb +80 -15
  143. data/spec/fear/success_spec.rb +99 -32
  144. data/spec/fear/try/mixin_spec.rb +19 -0
  145. data/spec/fear/try_pattern_match_spec.rb +37 -0
  146. data/spec/fear/try_pattern_matching_spec.rb +34 -0
  147. data/spec/fear/utils_spec.rb +16 -14
  148. data/spec/spec_helper.rb +13 -7
  149. data/spec/struct_pattern_matching_spec.rb +36 -0
  150. data/spec/struct_spec.rb +226 -0
  151. data/spec/support/dry_types.rb +6 -0
  152. metadata +320 -29
  153. data/.travis.yml +0 -9
  154. data/lib/fear/done.rb +0 -22
  155. data/lib/fear/for/evaluation_context.rb +0 -91
@@ -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,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module Extractor
5
+ class IdentifierMatcher < Matcher
6
+ # @!attribute name
7
+ # @return [Types::Strict::Symbol]
8
+
9
+ def defined_at?(_)
10
+ true
11
+ end
12
+
13
+ def bindings(other)
14
+ { name => other }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+
5
+ module Fear
6
+ module Extractor
7
+ # @abstract abstract matcher to inherit from.
8
+ class Matcher < OpenStruct
9
+ autoload :And, "fear/extractor/matcher/and"
10
+
11
+ # @param node [Fear::Extractor::Grammar::Node]
12
+ def initialize(node:, **attributes)
13
+ @input = node.input
14
+ @input_position = node.interval.first
15
+ super(attributes)
16
+ end
17
+ attr_reader :input_position, :input
18
+ private :input
19
+ private :input_position
20
+
21
+ def call(arg)
22
+ call_or_else(arg, &PartialFunction::EMPTY)
23
+ end
24
+
25
+ def and(other)
26
+ And.new(self, other)
27
+ end
28
+
29
+ # @param arg [any]
30
+ # @yield [arg] if function not defined
31
+ def call_or_else(arg)
32
+ if defined_at?(arg)
33
+ bindings(arg)
34
+ else
35
+ yield arg
36
+ end
37
+ end
38
+
39
+ # Shows why matcher has failed. Use it for debugging.
40
+ # @example
41
+ # Fear['[1, 2, _]'].failure_reason([1, 3, 4])
42
+ # # it will show that the second element hasn't match
43
+ #
44
+ def failure_reason(other)
45
+ if defined_at?(other)
46
+ Fear.none
47
+ else
48
+ Fear.some("Expected `#{other.inspect}` to match:\n#{input}\n#{"~" * input_position}^")
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+
5
+ module Fear
6
+ module Extractor
7
+ class Matcher < OpenStruct
8
+ # Combine two matchers, so both should pass
9
+ class And < Matcher
10
+ def initialize(matcher1, matcher2)
11
+ @matcher1 = matcher1
12
+ @matcher2 = matcher2
13
+ end
14
+ attr_reader :matcher1, :matcher2
15
+
16
+ def defined_at?(arg)
17
+ matcher1.defined_at?(arg) && matcher2.defined_at?(arg)
18
+ end
19
+
20
+ def bindings(arg)
21
+ matcher1.bindings(arg).merge(matcher2.bindings(arg))
22
+ end
23
+
24
+ def failure_reason(arg)
25
+ if matcher1.defined_at?(arg)
26
+ if matcher2.defined_at?(arg)
27
+ Fear.none
28
+ else
29
+ matcher2.failure_reason(arg)
30
+ end
31
+ else
32
+ matcher1.failure_reason(arg)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module Extractor
5
+ # Match against array splat, and capture rest of an array
6
+ # E.g. +[1, 2, *tail]+
7
+ #
8
+ class NamedArraySplatMatcher < ArraySplatMatcher
9
+ # @!attribute name
10
+ # @return [Types::Strict::Symbol]
11
+
12
+ def bindings(other)
13
+ { name => other }
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lru_redux"
4
+
5
+ module Fear
6
+ module Extractor
7
+ # Parse pattern. Used within +Fear[]+
8
+ class Pattern
9
+ DEFAULT_PATTERN_CACHE_SIZE = 10_000
10
+ private_constant :DEFAULT_PATTERN_CACHE_SIZE
11
+ @pattern_cache = LruRedux::Cache.new(ENV.fetch("FEAR_PATTERNS_CACHE_SIZE", DEFAULT_PATTERN_CACHE_SIZE))
12
+
13
+ class << self
14
+ attr_reader :pattern_cache
15
+ end
16
+
17
+ def initialize(pattern)
18
+ @matcher = compile_pattern(pattern)
19
+ end
20
+ attr_reader :matcher
21
+ private :matcher
22
+
23
+ private def compile_pattern(pattern)
24
+ self.class.pattern_cache.getset(pattern) do
25
+ compile_pattern_without_cache(pattern)
26
+ end
27
+ end
28
+
29
+ private def compile_pattern_without_cache(pattern)
30
+ parser = Extractor::GrammarParser.new
31
+ if (result = parser.parse(pattern))
32
+ result.to_matcher
33
+ else
34
+ raise PatternSyntaxError, syntax_error_message(parser, pattern)
35
+ end
36
+ end
37
+
38
+ def ===(other)
39
+ matcher.defined_at?(other)
40
+ end
41
+
42
+ def and_then(other)
43
+ Fear::PartialFunction::Combined.new(matcher, other)
44
+ end
45
+
46
+ def failure_reason(other)
47
+ matcher.failure_reason(other).get_or_else { "It matches" }
48
+ end
49
+
50
+ private def syntax_error_message(parser, pattern)
51
+ parser.failure_reason =~ /^(Expected .+) after/m
52
+ "#{Regexp.last_match(1).gsub("\n", "$NEWLINE")}:\n" +
53
+ pattern.split("\n")[parser.failure_line - 1] + "\n" \
54
+ "#{"~" * (parser.failure_column - 1)}^\n"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module Extractor
5
+ # Match and capture identifier with specific type. E.g. +foo : Integer+
6
+ #
7
+ class TypedIdentifierMatcher < Matcher
8
+ # @!attribute identifier
9
+ # @return [IdentifierMatcher]
10
+ # @!attribute type
11
+ # @return [ValueMatcher]
12
+
13
+ def defined_at?(other)
14
+ type.defined_at?(other)
15
+ end
16
+
17
+ def bindings(other)
18
+ { identifier.name => other }
19
+ end
20
+
21
+ def failure_reason(other)
22
+ type.failure_reason(other)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module Extractor
5
+ # Match against values -- true, false, 1, "foo" etc.
6
+ class ValueMatcher < Matcher
7
+ # @!attribute value
8
+ # @return [Any]
9
+
10
+ def defined_at?(arg)
11
+ value === arg
12
+ end
13
+
14
+ def bindings(_)
15
+ Utils::EMPTY_HASH
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fear
4
+ module ExtractorApi
5
+ # Allows to pattern match and extract matcher variables
6
+ #
7
+ # @param pattern [String]
8
+ # @return [Extractor::Pattern]
9
+ # @note it is not intended to be used by itself, rather then with partial functions
10
+ def [](pattern)
11
+ Extractor::Pattern.new(pattern)
12
+ end
13
+
14
+ # Register extractor for given class
15
+ # @!method register_extractor(*names, extractor)
16
+ # @param names [<Class, String>, Class, String] name of a class. You can also pass alias for the name
17
+ # @param extractor [Proc<any => Fear::Option>] proc taking any argument and returned Option
18
+ # of extracted value('s)
19
+ #
20
+ # @example
21
+ # register_extractor(Fear::Some, Fear.case(Fear::Some) { |some| some.get }.lift)
22
+ #
23
+ # register_extractor(User, Fear.case(User) { |user|} [user.id, user.email] , )
24
+ #
25
+ # @example registering an alias. Alias should be CamelCased string
26
+ # register_extractor(Fear::Some, 'Some', Fear.case(Fear::Some) { |some| some.get }.lift)
27
+ #
28
+ # # no you can extract Fear::Some's using Some alias
29
+ # m.case(Fear['Some(value : Integer)']) { |value:| value * 2 }
30
+ #
31
+ def register_extractor(*args)
32
+ Extractor.register_extractor(*args)
33
+ end
34
+ end
35
+ end
@@ -1,8 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Fear
2
4
  class Failure
3
5
  include Try
4
- include Dry::Equalizer(:exception)
5
6
  include RightBiased::Left
7
+ include FailurePatternMatch.mixin
8
+
9
+ EXTRACTOR = proc do |try|
10
+ if Fear::Failure === try
11
+ Fear.some([try.exception])
12
+ else
13
+ Fear.none
14
+ end
15
+ end
16
+ public_constant :EXTRACTOR
6
17
 
7
18
  # @param [StandardError]
8
19
  def initialize(exception)
@@ -23,13 +34,13 @@ module Fear
23
34
 
24
35
  # @raise
25
36
  def get
26
- fail exception
37
+ raise exception
27
38
  end
28
39
 
29
40
  # @return [Try] of calling block
30
41
  def or_else(*args)
31
42
  super
32
- rescue => error
43
+ rescue StandardError => error
33
44
  Failure.new(error)
34
45
  end
35
46
 
@@ -43,23 +54,25 @@ module Fear
43
54
  self
44
55
  end
45
56
 
46
- # @yieldparam [Exception]
47
- # @yieldreturn [Try]
48
- # @return [Try]
57
+ # @yieldparam [Fear::PatternMatch]
58
+ # @yieldreturn [Fear::Try]
59
+ # @return [Fear::Try]
49
60
  def recover_with
50
- yield(exception).tap do |result|
51
- Utils.assert_type!(result, Success, Failure)
52
- end
53
- rescue => error
61
+ Fear.matcher { |m| yield(m) }
62
+ .and_then { |result| result.tap { Utils.assert_type!(result, Success, Failure) } }
63
+ .call_or_else(exception) { self }
64
+ rescue StandardError => error
54
65
  Failure.new(error)
55
66
  end
56
67
 
57
- # @yieldparam [Exception]
68
+ # @yieldparam [Fear::PatternMatch]
58
69
  # @yieldreturn [any]
59
- # @return [Try]
70
+ # @return [Fear::Try]
60
71
  def recover
61
- Success.new(yield(exception))
62
- rescue => error
72
+ Fear.matcher { |m| yield(m) }
73
+ .and_then { |v| Success.new(v) }
74
+ .call_or_else(exception) { self }
75
+ rescue StandardError => error
63
76
  Failure.new(error)
64
77
  end
65
78
 
@@ -68,6 +81,12 @@ module Fear
68
81
  Left.new(exception)
69
82
  end
70
83
 
84
+ # @param other [Any]
85
+ # @return [Boolean]
86
+ def ==(other)
87
+ other.is_a?(Failure) && exception == other.exception
88
+ end
89
+
71
90
  # Used in case statement
72
91
  # @param other [any]
73
92
  # @return [Boolean]
@@ -78,5 +97,18 @@ module Fear
78
97
  super
79
98
  end
80
99
  end
100
+
101
+ # @return [String]
102
+ def inspect
103
+ "#<Fear::Failure exception=#{exception.inspect}>"
104
+ end
105
+
106
+ # @return [String]
107
+ alias to_s inspect
108
+
109
+ # @return [<StandardError>]
110
+ def deconstruct
111
+ [exception]
112
+ end
81
113
  end
82
114
  end