dry-monads 0.3.1 → 0.4.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.
- 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.
|