dry-monads 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +7 -15
- data/.yardopts +4 -0
- data/CHANGELOG.md +32 -3
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +7 -1
- data/dry-monads.gemspec +3 -2
- data/lib/dry/monads.rb +50 -27
- data/lib/dry/monads/either.rb +1 -193
- data/lib/dry/monads/errors.rb +15 -0
- data/lib/dry/monads/list.rb +8 -7
- data/lib/dry/monads/maybe.rb +44 -25
- data/lib/dry/monads/result.rb +250 -0
- data/lib/dry/monads/result/fixed.rb +34 -0
- data/lib/dry/monads/right_biased.rb +79 -6
- data/lib/dry/monads/try.rb +86 -42
- data/lib/dry/monads/version.rb +1 -1
- data/lib/json/add/dry/monads/maybe.rb +1 -1
- metadata +32 -6
@@ -0,0 +1,34 @@
|
|
1
|
+
module Dry::Monads
|
2
|
+
class Result
|
3
|
+
# @see Monads#Result
|
4
|
+
# @api private
|
5
|
+
class Fixed < Module
|
6
|
+
def self.[](error, **options)
|
7
|
+
new(error, **options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(error, **options)
|
11
|
+
@mod = Module.new do
|
12
|
+
define_method(:Failure) do |value|
|
13
|
+
if error === value
|
14
|
+
Failure.new(value)
|
15
|
+
else
|
16
|
+
raise InvalidFailureTypeError.new(value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def Success(value)
|
21
|
+
Success.new(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
def included(base)
|
28
|
+
super
|
29
|
+
|
30
|
+
base.include(@mod)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,12 +1,25 @@
|
|
1
1
|
require 'dry/core/constants'
|
2
|
+
require 'dry/core/deprecations'
|
3
|
+
|
4
|
+
require 'dry/monads/errors'
|
2
5
|
|
3
6
|
module Dry
|
4
7
|
module Monads
|
5
8
|
module RightBiased
|
9
|
+
# @api public
|
6
10
|
module Right
|
7
11
|
include Dry::Core::Constants
|
8
12
|
|
9
|
-
|
13
|
+
extend Dry::Core::Deprecations[:'dry-monads']
|
14
|
+
|
15
|
+
# Unwraps the underlying value
|
16
|
+
#
|
17
|
+
# @return [Object]
|
18
|
+
def value!
|
19
|
+
@value
|
20
|
+
end
|
21
|
+
|
22
|
+
deprecate :value, :value!
|
10
23
|
|
11
24
|
# Calls the passed in Proc object with value stored in self
|
12
25
|
# and returns the result.
|
@@ -24,10 +37,10 @@ module Dry
|
|
24
37
|
# @return [Object] result of calling proc or block on the internal value
|
25
38
|
def bind(*args, **kwargs)
|
26
39
|
if args.empty? && !kwargs.empty?
|
27
|
-
vargs, vkwargs = destructure(value)
|
40
|
+
vargs, vkwargs = destructure(@value)
|
28
41
|
kw = [kwargs.merge(vkwargs)]
|
29
42
|
else
|
30
|
-
vargs = [value]
|
43
|
+
vargs = [@value]
|
31
44
|
kw = kwargs.empty? ? EMPTY_ARRAY : [kwargs]
|
32
45
|
end
|
33
46
|
|
@@ -35,7 +48,7 @@ module Dry
|
|
35
48
|
yield(*vargs, *args, *kw)
|
36
49
|
else
|
37
50
|
obj, *rest = args
|
38
|
-
obj.
|
51
|
+
obj.(*vargs, *rest, *kw)
|
39
52
|
end
|
40
53
|
end
|
41
54
|
|
@@ -80,7 +93,33 @@ module Dry
|
|
80
93
|
#
|
81
94
|
# @return [Object]
|
82
95
|
def value_or(_val = nil)
|
83
|
-
value
|
96
|
+
@value
|
97
|
+
end
|
98
|
+
|
99
|
+
# Applies the stored value to the given argument if the argument has type of Right,
|
100
|
+
# otherwise returns the argument
|
101
|
+
#
|
102
|
+
# @example happy path
|
103
|
+
# create_user = Dry::Monads::Right(CreateUser.new)
|
104
|
+
# name = Right("John")
|
105
|
+
# create_user.apply(name) # equivalent to CreateUser.new.call("John")
|
106
|
+
#
|
107
|
+
# @example unhappy path
|
108
|
+
# name = Left(:name_missing)
|
109
|
+
# create_user.apply(name) # => Left(:name_missing)
|
110
|
+
#
|
111
|
+
# @return [RightBiased::Left,RightBiased::Right]
|
112
|
+
def apply(val)
|
113
|
+
unless @value.respond_to?(:call)
|
114
|
+
raise TypeError, "Cannot apply #{ val.inspect } to #{ @value.inspect }"
|
115
|
+
end
|
116
|
+
val.fmap { |unwrapped| curry.(unwrapped) }
|
117
|
+
end
|
118
|
+
|
119
|
+
# @param other [RightBiased]
|
120
|
+
# @return [Boolean]
|
121
|
+
def ===(other)
|
122
|
+
self.class == other.class && value! === other.value!
|
84
123
|
end
|
85
124
|
|
86
125
|
private
|
@@ -89,11 +128,37 @@ module Dry
|
|
89
128
|
def destructure(*args, **kwargs)
|
90
129
|
[args, kwargs]
|
91
130
|
end
|
131
|
+
|
132
|
+
# @api private
|
133
|
+
def curry
|
134
|
+
@curried ||=
|
135
|
+
begin
|
136
|
+
func = @value.is_a?(Proc) ? @value : @value.method(:call)
|
137
|
+
seq_args = func.parameters.count { |type, _| type == :req }
|
138
|
+
seq_args += 1 if func.parameters.any? { |type, _| type == :keyreq }
|
139
|
+
|
140
|
+
if seq_args > 1
|
141
|
+
func.curry
|
142
|
+
else
|
143
|
+
func
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
92
147
|
end
|
93
148
|
|
149
|
+
# @api public
|
94
150
|
module Left
|
151
|
+
extend Dry::Core::Deprecations[:'dry-monads']
|
152
|
+
|
95
153
|
attr_reader :value
|
96
154
|
|
155
|
+
deprecate :value, message: '.value is deprecated, use .value! instead'
|
156
|
+
|
157
|
+
# Raises an error on accessing internal value
|
158
|
+
def value!
|
159
|
+
raise UnwrapError.new(self)
|
160
|
+
end
|
161
|
+
|
97
162
|
# Ignores the input parameter and returns self. It exists to keep the interface
|
98
163
|
# identical to that of {RightBiased::Right}.
|
99
164
|
#
|
@@ -143,7 +208,7 @@ module Dry
|
|
143
208
|
|
144
209
|
# Returns the passed value
|
145
210
|
#
|
146
|
-
# @
|
211
|
+
# @return [Object]
|
147
212
|
def value_or(val = nil)
|
148
213
|
if block_given?
|
149
214
|
yield
|
@@ -151,6 +216,14 @@ module Dry
|
|
151
216
|
val
|
152
217
|
end
|
153
218
|
end
|
219
|
+
|
220
|
+
# Ignores the input parameter and returns self. It exists to keep the interface
|
221
|
+
# identical to that of {RightBiased::Right}.
|
222
|
+
#
|
223
|
+
# @return [RightBiased::Left]
|
224
|
+
def apply(*)
|
225
|
+
self
|
226
|
+
end
|
154
227
|
end
|
155
228
|
end
|
156
229
|
end
|
data/lib/dry/monads/try.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'dry/equalizer'
|
2
2
|
|
3
3
|
require 'dry/monads/right_biased'
|
4
|
+
require 'dry/monads/result'
|
5
|
+
require 'dry/monads/maybe'
|
4
6
|
|
5
7
|
module Dry
|
6
8
|
module Monads
|
@@ -12,40 +14,37 @@ module Dry
|
|
12
14
|
attr_reader :exception
|
13
15
|
|
14
16
|
# Calls the passed in proc object and if successful stores the result in a
|
15
|
-
# {Try::
|
16
|
-
# it in a {Try::
|
17
|
+
# {Try::Value} monad, but if one of the specified exceptions was raised it stores
|
18
|
+
# it in a {Try::Error} monad.
|
17
19
|
#
|
18
20
|
# @param exceptions [Array<Exception>] list of exceptions to be rescued
|
19
21
|
# @param f [Proc] the proc to be called
|
20
|
-
# @return [Try::
|
22
|
+
# @return [Try::Value, Try::Error]
|
21
23
|
def self.lift(exceptions, f)
|
22
|
-
|
24
|
+
Value.new(exceptions, f.call)
|
23
25
|
rescue *exceptions => e
|
24
|
-
|
26
|
+
Error.new(e)
|
25
27
|
end
|
26
28
|
|
27
|
-
# Returns true for an instance of a {Try::
|
28
|
-
def
|
29
|
-
is_a?
|
29
|
+
# Returns true for an instance of a {Try::Value} monad.
|
30
|
+
def value?
|
31
|
+
is_a?(Value)
|
30
32
|
end
|
33
|
+
alias_method :success?, :value?
|
31
34
|
|
32
|
-
# Returns true for an instance of a {Try::
|
33
|
-
def
|
34
|
-
is_a?
|
35
|
+
# Returns true for an instance of a {Try::Error} monad.
|
36
|
+
def error?
|
37
|
+
is_a?(Error)
|
35
38
|
end
|
39
|
+
alias_method :failure?, :error?
|
36
40
|
|
37
41
|
# Represents a result of a successful execution.
|
38
42
|
#
|
39
43
|
# @api public
|
40
|
-
class
|
41
|
-
include Dry::Equalizer(:value
|
44
|
+
class Value < Try
|
45
|
+
include Dry::Equalizer(:value!, :catchable)
|
42
46
|
include RightBiased::Right
|
43
47
|
|
44
|
-
# Using #or is not a good practice, you should process exceptions
|
45
|
-
# explicitly hence we don't offer an easy way to ignore them.
|
46
|
-
# Use Try#to_maybe if you're sure you need `#or` for `Try`.
|
47
|
-
undef :or
|
48
|
-
|
49
48
|
attr_reader :catchable
|
50
49
|
|
51
50
|
# @param exceptions [Array<Exception>] list of exceptions to be rescued
|
@@ -55,7 +54,7 @@ module Dry
|
|
55
54
|
@value = value
|
56
55
|
end
|
57
56
|
|
58
|
-
|
57
|
+
alias_method :bind_call, :bind
|
59
58
|
private :bind_call
|
60
59
|
|
61
60
|
# Calls the passed in Proc object with value stored in self
|
@@ -64,20 +63,20 @@ module Dry
|
|
64
63
|
# If proc is nil, it expects a block to be given and will yield to it.
|
65
64
|
#
|
66
65
|
# @example
|
67
|
-
# success = Dry::Monads::Try::
|
66
|
+
# success = Dry::Monads::Try::Value.new(ZeroDivisionError, 10)
|
68
67
|
# success.bind(->(n) { n / 2 }) # => 5
|
69
|
-
# success.bind { |n| n / 0 } # => Try::
|
68
|
+
# success.bind { |n| n / 0 } # => Try::Error(ZeroDivisionError: divided by 0)
|
70
69
|
#
|
71
70
|
# @param args [Array<Object>] arguments that will be passed to a block
|
72
71
|
# if one was given, otherwise the first
|
73
72
|
# value assumed to be a Proc (callable)
|
74
73
|
# object and the rest of args will be passed
|
75
74
|
# to this object along with the internal value
|
76
|
-
# @return [Object, Try::
|
77
|
-
def bind(*)
|
75
|
+
# @return [Object, Try::Error]
|
76
|
+
def bind(*args)
|
78
77
|
super
|
79
78
|
rescue *catchable => e
|
80
|
-
|
79
|
+
Error.new(e)
|
81
80
|
end
|
82
81
|
|
83
82
|
# Does the same thing as #bind except it also wraps the value
|
@@ -85,43 +84,43 @@ module Dry
|
|
85
84
|
# chaining of calls.
|
86
85
|
#
|
87
86
|
# @example
|
88
|
-
# success = Dry::Monads::Try::
|
87
|
+
# success = Dry::Monads::Try::Value.new(ZeroDivisionError, 10)
|
89
88
|
# success.fmap(&:succ).fmap(&:succ).value # => 12
|
90
89
|
# success.fmap(&:succ).fmap { |n| n / 0 }.fmap(&:succ).value # => nil
|
91
90
|
#
|
92
91
|
# @param args [Array<Object>] extra arguments for the block, arguments are being processes
|
93
92
|
# just as in #bind
|
94
|
-
# @return [Try::
|
93
|
+
# @return [Try::Value, Try::Error]
|
95
94
|
def fmap(*args, &block)
|
96
|
-
|
95
|
+
Value.new(catchable, bind_call(*args, &block))
|
97
96
|
rescue *catchable => e
|
98
|
-
|
97
|
+
Error.new(e)
|
99
98
|
end
|
100
99
|
|
101
100
|
# @return [Maybe]
|
102
101
|
def to_maybe
|
103
|
-
Dry::Monads::Maybe(value)
|
102
|
+
Dry::Monads::Maybe(@value)
|
104
103
|
end
|
105
104
|
|
106
|
-
# @return [
|
107
|
-
def
|
108
|
-
Dry::Monads::
|
105
|
+
# @return [Result::Success]
|
106
|
+
def to_result
|
107
|
+
Dry::Monads::Success(@value)
|
109
108
|
end
|
109
|
+
alias_method :to_either, :to_result
|
110
110
|
|
111
111
|
# @return [String]
|
112
112
|
def to_s
|
113
|
-
"Try::
|
113
|
+
"Try::Value(#{ @value.inspect })"
|
114
114
|
end
|
115
|
-
|
115
|
+
alias_method :inspect, :to_s
|
116
116
|
end
|
117
117
|
|
118
118
|
# Represents a result of a failed execution.
|
119
119
|
#
|
120
120
|
# @api public
|
121
|
-
class
|
121
|
+
class Error < Try
|
122
122
|
include Dry::Equalizer(:exception)
|
123
123
|
include RightBiased::Left
|
124
|
-
undef :or
|
125
124
|
|
126
125
|
# @param exception [Exception]
|
127
126
|
def initialize(exception)
|
@@ -133,16 +132,41 @@ module Dry
|
|
133
132
|
Dry::Monads::None()
|
134
133
|
end
|
135
134
|
|
136
|
-
# @return [
|
137
|
-
def
|
138
|
-
Dry::Monads::
|
135
|
+
# @return [Result::Failure]
|
136
|
+
def to_result
|
137
|
+
Dry::Monads::Failure(exception)
|
139
138
|
end
|
139
|
+
alias_method :to_either, :to_result
|
140
140
|
|
141
141
|
# @return [String]
|
142
142
|
def to_s
|
143
|
-
"Try::
|
143
|
+
"Try::Error(#{ exception.class }: #{ exception.message })"
|
144
|
+
end
|
145
|
+
alias_method :inspect, :to_s
|
146
|
+
|
147
|
+
# If a block is given passes internal value to it and returns the result,
|
148
|
+
# otherwise simply returns the first argument.
|
149
|
+
#
|
150
|
+
# @example
|
151
|
+
# Try(ZeroDivisionError) { 1 / 0 }.or { "zero!" } # => "zero!"
|
152
|
+
#
|
153
|
+
# @param args [Array<Object>] arguments that will be passed to a block
|
154
|
+
# if one was given, otherwise the first
|
155
|
+
# value will be returned
|
156
|
+
# @return [Object]
|
157
|
+
def or(*args)
|
158
|
+
if block_given?
|
159
|
+
yield(exception, *args)
|
160
|
+
else
|
161
|
+
args[0]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# @param other [Try]
|
166
|
+
# @return [Boolean]
|
167
|
+
def ===(other)
|
168
|
+
Error === other && exception === other.exception
|
144
169
|
end
|
145
|
-
alias inspect to_s
|
146
170
|
end
|
147
171
|
|
148
172
|
# A module that can be included for easier access to Try monads.
|
@@ -165,14 +189,34 @@ module Dry
|
|
165
189
|
|
166
190
|
DEFAULT_EXCEPTIONS = [StandardError].freeze
|
167
191
|
|
168
|
-
# A convenience wrapper for {Try.lift}.
|
192
|
+
# A convenience wrapper for {Monads::Try.lift}.
|
169
193
|
# If no exceptions are provided it falls back to StandardError.
|
170
194
|
# In general, relying on this behaviour is not recommended as it can lead to unnoticed
|
171
195
|
# bugs and it is always better to explicitly specify a list of exceptions if possible.
|
196
|
+
#
|
197
|
+
# @param exceptions [Array<Exception>]
|
172
198
|
def Try(*exceptions, &f)
|
173
199
|
catchable = exceptions.empty? ? DEFAULT_EXCEPTIONS : exceptions.flatten
|
174
200
|
Try.lift(catchable, f)
|
175
201
|
end
|
202
|
+
|
203
|
+
def Value(value = Undefined, exceptions = DEFAULT_EXCEPTIONS, &block)
|
204
|
+
if value.equal?(Undefined)
|
205
|
+
raise ArgumentError, 'No value given' if block.nil?
|
206
|
+
Try::Value.new(exceptions, block)
|
207
|
+
else
|
208
|
+
Try::Value.new(exceptions, value)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def Error(error = Undefined, &block)
|
213
|
+
if error.equal?(Undefined)
|
214
|
+
raise ArgumentError, 'No value given' if block.nil?
|
215
|
+
Try::Error.new(block)
|
216
|
+
else
|
217
|
+
Try::Error.new(error)
|
218
|
+
end
|
219
|
+
end
|
176
220
|
end
|
177
221
|
end
|
178
222
|
end
|
data/lib/dry/monads/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-monads
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nikita Shilnikov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-equalizer
|
@@ -28,16 +28,22 @@ dependencies:
|
|
28
28
|
name: dry-core
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.3'
|
31
34
|
- - ">="
|
32
35
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
36
|
+
version: 0.3.3
|
34
37
|
type: :runtime
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.3'
|
38
44
|
- - ">="
|
39
45
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
46
|
+
version: 0.3.3
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: bundler
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +86,20 @@ dependencies:
|
|
80
86
|
- - ">="
|
81
87
|
- !ruby/object:Gem::Version
|
82
88
|
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: dry-types
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0.12'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0.12'
|
83
103
|
description: Common monads for Ruby.
|
84
104
|
email:
|
85
105
|
- fg@flashgordon.ru
|
@@ -91,7 +111,9 @@ files:
|
|
91
111
|
- ".gitignore"
|
92
112
|
- ".rspec"
|
93
113
|
- ".travis.yml"
|
114
|
+
- ".yardopts"
|
94
115
|
- CHANGELOG.md
|
116
|
+
- CONTRIBUTING.md
|
95
117
|
- Gemfile
|
96
118
|
- LICENSE
|
97
119
|
- README.md
|
@@ -102,13 +124,17 @@ files:
|
|
102
124
|
- lib/dry-monads.rb
|
103
125
|
- lib/dry/monads.rb
|
104
126
|
- lib/dry/monads/either.rb
|
127
|
+
- lib/dry/monads/errors.rb
|
105
128
|
- lib/dry/monads/list.rb
|
106
129
|
- lib/dry/monads/maybe.rb
|
130
|
+
- lib/dry/monads/result.rb
|
131
|
+
- lib/dry/monads/result/fixed.rb
|
107
132
|
- lib/dry/monads/right_biased.rb
|
108
133
|
- lib/dry/monads/transformer.rb
|
109
134
|
- lib/dry/monads/try.rb
|
110
135
|
- lib/dry/monads/version.rb
|
111
136
|
- lib/json/add/dry/monads/maybe.rb
|
137
|
+
- log/.gitkeep
|
112
138
|
homepage: https://github.com/dry-rb/dry-monads
|
113
139
|
licenses:
|
114
140
|
- MIT
|
@@ -122,7 +148,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
122
148
|
requirements:
|
123
149
|
- - ">="
|
124
150
|
- !ruby/object:Gem::Version
|
125
|
-
version: 2.
|
151
|
+
version: 2.2.0
|
126
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
153
|
requirements:
|
128
154
|
- - ">="
|
@@ -130,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
156
|
version: '0'
|
131
157
|
requirements: []
|
132
158
|
rubyforge_project:
|
133
|
-
rubygems_version: 2.6.
|
159
|
+
rubygems_version: 2.6.11
|
134
160
|
signing_key:
|
135
161
|
specification_version: 4
|
136
162
|
summary: Common monads for Ruby.
|