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