fear 0.0.1
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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.rubocop.yml +32 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +32 -0
- data/Rakefile +1 -0
- data/fear.gemspec +29 -0
- data/lib/fear/either.rb +145 -0
- data/lib/fear/failure.rb +65 -0
- data/lib/fear/for.rb +104 -0
- data/lib/fear/left.rb +52 -0
- data/lib/fear/none.rb +23 -0
- data/lib/fear/option.rb +112 -0
- data/lib/fear/right.rb +56 -0
- data/lib/fear/right_biased.rb +155 -0
- data/lib/fear/some.rb +42 -0
- data/lib/fear/success.rb +81 -0
- data/lib/fear/try.rb +127 -0
- data/lib/fear/utils.rb +25 -0
- data/lib/fear/version.rb +3 -0
- data/lib/fear.rb +31 -0
- data/spec/fear/failure_spec.rb +78 -0
- data/spec/fear/for_spec.rb +70 -0
- data/spec/fear/left_spec.rb +79 -0
- data/spec/fear/none_spec.rb +37 -0
- data/spec/fear/option_spec.rb +32 -0
- data/spec/fear/right_biased/left.rb +67 -0
- data/spec/fear/right_biased/right.rb +93 -0
- data/spec/fear/right_spec.rb +79 -0
- data/spec/fear/some_spec.rb +48 -0
- data/spec/fear/success_spec.rb +83 -0
- data/spec/fear/utils_spec.rb +60 -0
- data/spec/spec_helper.rb +77 -0
- metadata +175 -0
data/lib/fear/right.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Fear
|
2
|
+
class Right
|
3
|
+
include Either
|
4
|
+
include RightBiased::Right
|
5
|
+
|
6
|
+
# Returns `Left(default)` if the given predicate
|
7
|
+
# does not hold for the right value, otherwise, returns `Right`.
|
8
|
+
#
|
9
|
+
# @param default [Proc, any]
|
10
|
+
# @return [Either]
|
11
|
+
#
|
12
|
+
def detect(default)
|
13
|
+
if yield(value)
|
14
|
+
self
|
15
|
+
else
|
16
|
+
Left.new(Utils.return_or_call_proc(default))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Left] value in `Left`
|
21
|
+
def swap
|
22
|
+
Left.new(value)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param reduce_right [Proc] the function to apply if this is a `Right`
|
26
|
+
# @return [any] Applies `reduce_right` to the value.
|
27
|
+
#
|
28
|
+
def reduce(_, reduce_right)
|
29
|
+
reduce_right.call(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Joins an `Either` through `Right`.
|
33
|
+
#
|
34
|
+
# This method requires that the right side of this `Either` is itself an
|
35
|
+
# Either type.
|
36
|
+
#
|
37
|
+
# This method, and `join_left`, are analogous to `Option#flatten`
|
38
|
+
#
|
39
|
+
# @return [Either]
|
40
|
+
# @raise [TypeError] if it does not contain `Either`.
|
41
|
+
#
|
42
|
+
def join_right
|
43
|
+
value.tap do |v|
|
44
|
+
Utils.assert_type!(v, Either)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Joins an `Either` through `Left`.
|
49
|
+
#
|
50
|
+
# @return [self]
|
51
|
+
#
|
52
|
+
def join_left
|
53
|
+
self
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module Fear
|
2
|
+
module RightBiased
|
3
|
+
# Performs necessary interface and type checks.
|
4
|
+
#
|
5
|
+
module Interface
|
6
|
+
# Returns the value from this `RightBiased::Right` or the given argument if
|
7
|
+
# this is a `RightBiased::Left`.
|
8
|
+
def get_or_else(*args, &block)
|
9
|
+
Utils.assert_arg_or_block!('get_or_else', *args, &block)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def flat_map
|
14
|
+
super.tap do |result|
|
15
|
+
Utils.assert_type!(result, left_class, right_class)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Ensures that returned value either left, or right.
|
20
|
+
def detect(*)
|
21
|
+
super.tap do |result|
|
22
|
+
Utils.assert_type!(result, left_class, right_class)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Right
|
28
|
+
class << self
|
29
|
+
def included(base)
|
30
|
+
base.prepend Interface
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @!method get_or_else(default)
|
35
|
+
# @param default [any]
|
36
|
+
# @return [any] the `#value`.
|
37
|
+
#
|
38
|
+
# @!method get_or_else
|
39
|
+
# @return [any] the `#value`.
|
40
|
+
#
|
41
|
+
def get_or_else(*_args)
|
42
|
+
value
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [any]
|
46
|
+
# @return [Boolean] `true` if it has an element that is equal
|
47
|
+
# (as determined by `==`) to `other_value`, `false` otherwise.
|
48
|
+
#
|
49
|
+
def include?(other_value)
|
50
|
+
value == other_value
|
51
|
+
end
|
52
|
+
|
53
|
+
# Executes the given side-effecting block.
|
54
|
+
#
|
55
|
+
# @return [self]
|
56
|
+
#
|
57
|
+
def each
|
58
|
+
yield(value)
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Maps the value using given block.
|
63
|
+
#
|
64
|
+
# @return [RightBiased::Right]
|
65
|
+
#
|
66
|
+
def map
|
67
|
+
self.class.new(yield(value))
|
68
|
+
end
|
69
|
+
|
70
|
+
# Binds the given function across `RightBiased::Right`.
|
71
|
+
#
|
72
|
+
# @return [RightBiased::Left, RightBiased::Right]
|
73
|
+
#
|
74
|
+
def flat_map
|
75
|
+
yield(value)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Array] containing value
|
79
|
+
def to_a
|
80
|
+
[value]
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [Option] containing value
|
84
|
+
def to_option
|
85
|
+
Some.new(value)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [Boolean] true if value satisfies predicate.
|
89
|
+
def any?
|
90
|
+
yield(value)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
module Left
|
95
|
+
prepend Interface
|
96
|
+
include Utils
|
97
|
+
# @!method get_or_else(default)
|
98
|
+
# @param default [any]
|
99
|
+
# @return [any] default value
|
100
|
+
#
|
101
|
+
# @!method get_or_else
|
102
|
+
# @return [any] result of evaluating a block.
|
103
|
+
#
|
104
|
+
def get_or_else(*args)
|
105
|
+
args.fetch(0) { yield }
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param [any]
|
109
|
+
# @return [false]
|
110
|
+
#
|
111
|
+
def include?(_)
|
112
|
+
false
|
113
|
+
end
|
114
|
+
|
115
|
+
# Ignores the given side-effecting block and return self.
|
116
|
+
#
|
117
|
+
# @return [RightBiased::Left]
|
118
|
+
#
|
119
|
+
def each
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
# Ignores the given block and return self.
|
124
|
+
#
|
125
|
+
# @return [RightBiased::Left]
|
126
|
+
#
|
127
|
+
def map
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
# Ignores the given block and return self.
|
132
|
+
#
|
133
|
+
# @return [RightBiased::Left]
|
134
|
+
#
|
135
|
+
def flat_map
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [Array] empty array
|
140
|
+
def to_a
|
141
|
+
[]
|
142
|
+
end
|
143
|
+
|
144
|
+
# @return [None]
|
145
|
+
def to_option
|
146
|
+
None.new
|
147
|
+
end
|
148
|
+
|
149
|
+
# @return [false]
|
150
|
+
def any?
|
151
|
+
false
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/lib/fear/some.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Fear
|
2
|
+
class Some
|
3
|
+
include Option
|
4
|
+
include Dry::Equalizer(:get)
|
5
|
+
include RightBiased::Right
|
6
|
+
|
7
|
+
attr_reader :value
|
8
|
+
protected :value
|
9
|
+
|
10
|
+
def initialize(value)
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return option's value
|
15
|
+
def get
|
16
|
+
@value
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Option] self if this `Option` is nonempty and
|
20
|
+
# applying the `predicate` to this option's value
|
21
|
+
# returns true. Otherwise, return `None`.
|
22
|
+
#
|
23
|
+
def detect
|
24
|
+
if yield(value)
|
25
|
+
self
|
26
|
+
else
|
27
|
+
None.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Option] if applying the `predicate` to this
|
32
|
+
# option's value returns false. Otherwise, return `None`.
|
33
|
+
#
|
34
|
+
def reject
|
35
|
+
if yield(value)
|
36
|
+
None.new
|
37
|
+
else
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/fear/success.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
module Fear
|
2
|
+
class Success
|
3
|
+
include Try
|
4
|
+
include Dry::Equalizer(:value)
|
5
|
+
include RightBiased::Right
|
6
|
+
|
7
|
+
attr_reader :value
|
8
|
+
protected :value
|
9
|
+
|
10
|
+
def initialize(value)
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def get
|
15
|
+
@value
|
16
|
+
end
|
17
|
+
|
18
|
+
def success?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Success] self
|
23
|
+
def or_else
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# Transforms a nested `Try`, ie, a `Success` of `Success``,
|
28
|
+
# into an un-nested `Try`, ie, a `Success`.
|
29
|
+
# @return [Try]
|
30
|
+
#
|
31
|
+
def flatten
|
32
|
+
if value.is_a?(Try)
|
33
|
+
value.flatten
|
34
|
+
else
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Converts this to a `Failure` if the predicate
|
40
|
+
# is not satisfied.
|
41
|
+
# @yieldparam [any] value
|
42
|
+
# @yieldreturn [Boolean]
|
43
|
+
# @return [Try]
|
44
|
+
#
|
45
|
+
def detect
|
46
|
+
if yield(value)
|
47
|
+
self
|
48
|
+
else
|
49
|
+
fail NoSuchElementError, "Predicate does not hold for `#{value}`"
|
50
|
+
end
|
51
|
+
rescue => error
|
52
|
+
Failure.new(error)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Success] self
|
56
|
+
#
|
57
|
+
def recover_with
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Success] self
|
62
|
+
#
|
63
|
+
def recover
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Try]
|
68
|
+
def map
|
69
|
+
super
|
70
|
+
rescue => error
|
71
|
+
Failure.new(error)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [Try]
|
75
|
+
def flat_map
|
76
|
+
super
|
77
|
+
rescue => error
|
78
|
+
Failure.new(error)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/fear/try.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
module Fear
|
2
|
+
# The `Try` represents a computation that may either result
|
3
|
+
# in an exception, or return a successfully computed value.
|
4
|
+
#
|
5
|
+
# Instances of `Try`, are either an instance of `Success` or
|
6
|
+
# `Failure`.
|
7
|
+
#
|
8
|
+
# For example, `Try` can be used to perform division on a
|
9
|
+
# user-defined input, without the need to do explicit
|
10
|
+
# exception-handling in all of the places that an exception
|
11
|
+
# might occur.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# dividend = Try { params[:dividend].to_i }
|
15
|
+
# divisor = Try { params[:divisor].to_i }
|
16
|
+
# problem = dividend.flat_map { |x| divisor.map { |y| x / y }
|
17
|
+
#
|
18
|
+
# if problem.success?
|
19
|
+
# puts "Result of #{dividend.get} / #{divisor.get} is: #{problem.get}"
|
20
|
+
# else
|
21
|
+
# puts "You must've divided by zero or entered something wrong. Try again"
|
22
|
+
# puts "Info from the exception: #{problem.exception.message}"
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# An important property of `Try` shown in the above example is its
|
26
|
+
# ability to `pipeline`, or chain, operations, catching exceptions
|
27
|
+
# along the way. The `flat_map` and `map` combinators in the above
|
28
|
+
# example each essentially pass off either their successfully completed
|
29
|
+
# value, wrapped in the `Success` type for it to be further operated
|
30
|
+
# upon by the next combinator in the chain, or the exception wrapped
|
31
|
+
# in the `Failure` type usually to be simply passed on down the chain.
|
32
|
+
# Combinators such as `rescue` and `recover` are designed to provide some
|
33
|
+
# type of default behavior in the case of failure.
|
34
|
+
#
|
35
|
+
# @note only non-fatal exceptions are caught by the combinators on `Try`.
|
36
|
+
# Serious system errors, on the other hand, will be thrown.
|
37
|
+
#
|
38
|
+
# @note all `Try` combinators will catch exceptions and return failure
|
39
|
+
# unless otherwise specified in the documentation.
|
40
|
+
#
|
41
|
+
# @example #or_else
|
42
|
+
# Success(42).or_else { -1 } #=> Success(42)
|
43
|
+
# Failure(ArgumentError.new).or_else { -1 } #=> Success(-1)
|
44
|
+
# Failure(ArgumentError.new).or_else { 1/0 } #=> Failure(ZeroDivisionError.new('divided by 0'))
|
45
|
+
#
|
46
|
+
# @example #flatten
|
47
|
+
# Success(42).flatten #=> Success(42)
|
48
|
+
# Success(Success(42)).flatten #=> Success(42)
|
49
|
+
# Success(Failure(ArgumentError.new)).flatten #=> Failure(ArgumentError.new)
|
50
|
+
# Failure(ArgumentError.new).flatten { -1 } #=> Failure(ArgumentError.new)
|
51
|
+
#
|
52
|
+
# @example #map
|
53
|
+
# Success(42).map { |v| v/2 } #=> Success(21)
|
54
|
+
# Failure(ArgumentError.new).map { |v| v/2 } #=> Failure(ArgumentError.new)
|
55
|
+
#
|
56
|
+
# @example #detect
|
57
|
+
# Success(42).detect { |v| v > 40 }
|
58
|
+
# #=> Success(21)
|
59
|
+
# Success(42).detect { |v| v < 40 }
|
60
|
+
# #=> Failure(NoSuchElementError.new("Predicate does not hold for 42"))
|
61
|
+
# Failure(ArgumentError.new).detect { |v| v < 40 }
|
62
|
+
# #=> Failure(ArgumentError.new)
|
63
|
+
#
|
64
|
+
# @example #recover_with
|
65
|
+
# Success(42).recover_with { |e| Success(e.massage) }
|
66
|
+
# #=> Success(42)
|
67
|
+
# Failure(ArgumentError.new).recover_with { |e| Success(e.massage) }
|
68
|
+
# #=> Success('ArgumentError')
|
69
|
+
# Failure(ArgumentError.new).recover_with { |e| fail }
|
70
|
+
# #=> Failure(RuntimeError)
|
71
|
+
#
|
72
|
+
# @example #recover
|
73
|
+
# Success(42).recover { |e| e.massage }
|
74
|
+
# #=> Success(42)
|
75
|
+
# Failure(ArgumentError.new).recover { |e| e.massage }
|
76
|
+
# #=> Success('ArgumentError')
|
77
|
+
# Failure(ArgumentError.new).recover { |e| fail }
|
78
|
+
# #=> Failure(RuntimeError)
|
79
|
+
#
|
80
|
+
# @author based on Twitter's original implementation.
|
81
|
+
# @see https://github.com/scala/scala/blob/2.11.x/src/library/scala/util/Try.scala
|
82
|
+
#
|
83
|
+
module Try
|
84
|
+
def left_class
|
85
|
+
Failure
|
86
|
+
end
|
87
|
+
|
88
|
+
def right_class
|
89
|
+
Success
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [true, false] `true` if the `Try` is a `Failure`,
|
93
|
+
# `false` otherwise.
|
94
|
+
#
|
95
|
+
def failure?
|
96
|
+
!success?
|
97
|
+
end
|
98
|
+
|
99
|
+
module Mixin
|
100
|
+
# Constructs a `Try` using the block. This
|
101
|
+
# method will ensure any non-fatal exception is caught and a
|
102
|
+
# `Failure` object is returned.
|
103
|
+
# @return [Try]
|
104
|
+
#
|
105
|
+
def Try
|
106
|
+
Success.new(yield)
|
107
|
+
rescue StandardError => error
|
108
|
+
Failure.new(error)
|
109
|
+
end
|
110
|
+
|
111
|
+
# @param exception [StandardError]
|
112
|
+
# @return [Failure]
|
113
|
+
#
|
114
|
+
def Failure(exception)
|
115
|
+
fail TypeError, "not an error: #{exception}" unless exception.is_a?(StandardError)
|
116
|
+
Failure.new(exception)
|
117
|
+
end
|
118
|
+
|
119
|
+
# @param value [any]
|
120
|
+
# @return [Success]
|
121
|
+
#
|
122
|
+
def Success(value)
|
123
|
+
Success.new(value)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/fear/utils.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Fear
|
2
|
+
module Utils
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def assert_arg_or_block!(method_name, *args)
|
6
|
+
unless block_given? ^ args.any?
|
7
|
+
fail ArgumentError, "##{method_name} accepts either one argument or block"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_type!(value, *types)
|
12
|
+
if types.none? { |type| value.is_a?(type) }
|
13
|
+
fail TypeError, "expected `#{value}` to be of #{types.join(', ')} class"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def return_or_call_proc(value)
|
18
|
+
if value.respond_to?(:call)
|
19
|
+
value.call
|
20
|
+
else
|
21
|
+
value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/fear/version.rb
ADDED
data/lib/fear.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'dry-equalizer'
|
2
|
+
require 'fear/version'
|
3
|
+
|
4
|
+
module Fear
|
5
|
+
Error = Class.new(StandardError)
|
6
|
+
IllegalStateException = Class.new(Error)
|
7
|
+
NoSuchElementError = Class.new(Error)
|
8
|
+
|
9
|
+
autoload :For, 'fear/for'
|
10
|
+
autoload :Utils, 'fear/utils'
|
11
|
+
autoload :RightBiased, 'fear/right_biased'
|
12
|
+
|
13
|
+
autoload :Option, 'fear/option'
|
14
|
+
autoload :Some, 'fear/some'
|
15
|
+
autoload :None, 'fear/none'
|
16
|
+
|
17
|
+
autoload :Try, 'fear/try'
|
18
|
+
autoload :Success, 'fear/success'
|
19
|
+
autoload :Failure, 'fear/failure'
|
20
|
+
|
21
|
+
autoload :Either, 'fear/either'
|
22
|
+
autoload :Left, 'fear/left'
|
23
|
+
autoload :Right, 'fear/right'
|
24
|
+
|
25
|
+
module Mixin
|
26
|
+
include Either::Mixin
|
27
|
+
include For::Mixin
|
28
|
+
include Option::Mixin
|
29
|
+
include Try::Mixin
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
RSpec.describe Fear::Failure do
|
2
|
+
let(:failure) { described_class.new(RuntimeError.new('error')) }
|
3
|
+
|
4
|
+
it_behaves_like Fear::RightBiased::Left do
|
5
|
+
let(:left) { failure }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#success?' do
|
9
|
+
subject { failure }
|
10
|
+
it { is_expected.not_to be_success }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#get' do
|
14
|
+
subject { proc { failure.get } }
|
15
|
+
it { is_expected.to raise_error(RuntimeError, 'error') }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#or_else' do
|
19
|
+
context 'default does not fail' do
|
20
|
+
subject { failure.or_else { 'value' } }
|
21
|
+
it { is_expected.to eq(Fear::Success.new('value')) }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'default fails with error' do
|
25
|
+
subject(:or_else) { failure.or_else { fail 'unexpected error' } }
|
26
|
+
it { is_expected.to be_kind_of(described_class) }
|
27
|
+
it { expect { or_else.get }.to raise_error(RuntimeError, 'unexpected error') }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#flatten' do
|
32
|
+
subject { failure.flatten }
|
33
|
+
it { is_expected.to eq(failure) }
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#detect' do
|
37
|
+
subject { failure.detect { |v| v == 'value' } }
|
38
|
+
it { is_expected.to eq(failure) }
|
39
|
+
end
|
40
|
+
|
41
|
+
context '#recover_with' do
|
42
|
+
context 'block does not fail' do
|
43
|
+
subject do
|
44
|
+
failure.recover_with do |error|
|
45
|
+
Fear::Success.new(error.message)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns result of evaluation of the block against the error' do
|
50
|
+
is_expected.to eq(Fear::Success.new('error'))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'block fails' do
|
55
|
+
subject(:recover_with) { failure.recover_with { fail 'unexpected error' } }
|
56
|
+
|
57
|
+
it { is_expected.to be_kind_of(described_class) }
|
58
|
+
it { expect { recover_with.get }.to raise_error(RuntimeError, 'unexpected error') }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context '#recover' do
|
63
|
+
context 'block does not fail' do
|
64
|
+
subject { failure.recover(&:message) }
|
65
|
+
|
66
|
+
it 'returns Success of evaluation of the block against the error' do
|
67
|
+
is_expected.to eq(Fear::Success.new('error'))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'block fails' do
|
72
|
+
subject(:recover) { failure.recover { fail 'unexpected error' } }
|
73
|
+
|
74
|
+
it { is_expected.to be_kind_of(described_class) }
|
75
|
+
it { expect { recover.get }.to raise_error(RuntimeError, 'unexpected error') }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
RSpec.describe Fear::For do
|
2
|
+
include Fear::For::Mixin
|
3
|
+
|
4
|
+
context 'unary' do
|
5
|
+
context 'Some' do
|
6
|
+
subject do
|
7
|
+
For(a: Fear::Some.new(2)) { a * 2 }
|
8
|
+
end
|
9
|
+
|
10
|
+
it { is_expected.to eq(Fear::Some.new(4)) }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'None' do
|
14
|
+
subject do
|
15
|
+
For(a: Fear::None.new) { a * 2 }
|
16
|
+
end
|
17
|
+
|
18
|
+
it { is_expected.to eq(Fear::None.new) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'arrays' do
|
23
|
+
subject do
|
24
|
+
For(a: [1, 2], b: [2, 3], c: [3, 4]) do
|
25
|
+
a * b * c
|
26
|
+
end
|
27
|
+
end
|
28
|
+
it { is_expected.to eq([6, 8, 9, 12, 12, 16, 18, 24]) }
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'ternary' do
|
32
|
+
subject do
|
33
|
+
For(a: first, b: second, c: third) do
|
34
|
+
a * b * c
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'all Same' do
|
39
|
+
let(:first) { Fear::Some.new(2) }
|
40
|
+
let(:second) { Fear::Some.new(3) }
|
41
|
+
let(:third) { Fear::Some.new(4) }
|
42
|
+
|
43
|
+
it { is_expected.to eq(Fear::Some.new(24)) }
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'first None' do
|
47
|
+
let(:first) { Fear::None.new }
|
48
|
+
let(:second) { Fear::Some.new(3) }
|
49
|
+
let(:third) { Fear::Some.new(4) }
|
50
|
+
|
51
|
+
it { is_expected.to eq(Fear::None.new) }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'second None' do
|
55
|
+
let(:first) { Fear::Some.new(2) }
|
56
|
+
let(:second) { Fear::None.new }
|
57
|
+
let(:third) { Fear::Some.new(4) }
|
58
|
+
|
59
|
+
it { is_expected.to eq(Fear::None.new) }
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'last None' do
|
63
|
+
let(:first) { Fear::Some.new(2) }
|
64
|
+
let(:second) { Fear::Some.new(3) }
|
65
|
+
let(:third) { Fear::None.new }
|
66
|
+
|
67
|
+
it { is_expected.to eq(Fear::None.new) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|