dry-monads 1.3.5 → 1.6.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/CHANGELOG.md +161 -80
- data/LICENSE +1 -1
- data/README.md +5 -4
- data/dry-monads.gemspec +30 -30
- data/lib/dry/monads/all.rb +2 -3
- data/lib/dry/monads/constants.rb +0 -2
- data/lib/dry/monads/curry.rb +2 -2
- data/lib/dry/monads/do/all.rb +35 -18
- data/lib/dry/monads/do.rb +48 -20
- data/lib/dry/monads/errors.rb +8 -5
- data/lib/dry/monads/lazy.rb +13 -5
- data/lib/dry/monads/list.rb +27 -37
- data/lib/dry/monads/maybe.rb +85 -26
- data/lib/dry/monads/registry.rb +20 -20
- data/lib/dry/monads/result/fixed.rb +31 -24
- data/lib/dry/monads/result.rb +37 -19
- data/lib/dry/monads/right_biased.rb +38 -31
- data/lib/dry/monads/task.rb +25 -28
- data/lib/dry/monads/transformer.rb +2 -1
- data/lib/dry/monads/traverse.rb +5 -1
- data/lib/dry/monads/try.rb +45 -18
- data/lib/dry/monads/unit.rb +9 -3
- data/lib/dry/monads/validated.rb +18 -18
- data/lib/dry/monads/version.rb +1 -1
- data/lib/dry/monads.rb +25 -4
- data/lib/dry-monads.rb +1 -1
- data/lib/json/add/dry/monads/maybe.rb +5 -5
- metadata +22 -68
- data/.codeclimate.yml +0 -12
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
- data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -30
- data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
- data/.github/workflows/ci.yml +0 -52
- data/.github/workflows/docsite.yml +0 -34
- data/.github/workflows/sync_configs.yml +0 -56
- data/.gitignore +0 -10
- data/.rspec +0 -4
- data/.rubocop.yml +0 -101
- data/.yardopts +0 -4
- data/CODE_OF_CONDUCT.md +0 -13
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -19
- data/Gemfile.devtools +0 -14
- data/Rakefile +0 -8
- data/bin/.gitkeep +0 -0
- data/bin/console +0 -17
- data/bin/setup +0 -7
- data/docsite/source/case-equality.html.md +0 -42
- data/docsite/source/do-notation.html.md +0 -207
- data/docsite/source/getting-started.html.md +0 -142
- data/docsite/source/index.html.md +0 -179
- data/docsite/source/list.html.md +0 -87
- data/docsite/source/maybe.html.md +0 -146
- data/docsite/source/pattern-matching.html.md +0 -68
- data/docsite/source/result.html.md +0 -190
- data/docsite/source/task.html.md +0 -126
- data/docsite/source/tracing-failures.html.md +0 -32
- data/docsite/source/try.html.md +0 -76
- data/docsite/source/unit.html.md +0 -36
- data/docsite/source/validated.html.md +0 -88
- data/lib/dry/monads/either.rb +0 -66
- data/log/.gitkeep +0 -0
- data/project.yml +0 -2
data/lib/dry/monads/do/all.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'dry/monads/do'
|
4
|
-
|
5
3
|
module Dry
|
6
4
|
module Monads
|
7
5
|
module Do
|
@@ -68,9 +66,10 @@ module Dry
|
|
68
66
|
tracker = self
|
69
67
|
|
70
68
|
module_eval do
|
69
|
+
private
|
70
|
+
|
71
71
|
define_method(:method_added) do |method|
|
72
72
|
super(method)
|
73
|
-
|
74
73
|
tracker.wrap_method(self, method)
|
75
74
|
end
|
76
75
|
|
@@ -93,20 +92,37 @@ module Dry
|
|
93
92
|
end
|
94
93
|
|
95
94
|
def wrap_method(target, method)
|
96
|
-
Do.
|
95
|
+
visibility = Do.method_visibility(target, method)
|
96
|
+
Do.wrap_method(wrappers[target], method, visibility)
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
-
|
101
|
-
|
102
|
-
|
100
|
+
class << self
|
101
|
+
# @api private
|
102
|
+
def included(base)
|
103
|
+
super
|
104
|
+
|
105
|
+
wrappers = ::Hash.new { _1[_2] = ::Module.new }
|
106
|
+
tracker = MethodTracker.new(wrappers)
|
107
|
+
base.extend(tracker)
|
108
|
+
base.extend(InstanceMixin) unless base.is_a?(::Class)
|
109
|
+
wrap_defined_methods(base, wrappers[base])
|
110
|
+
end
|
103
111
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
112
|
+
# @api private
|
113
|
+
def wrap_defined_methods(klass, target)
|
114
|
+
klass.public_instance_methods(false).each do |m|
|
115
|
+
Do.wrap_method(target, m, :public)
|
116
|
+
end
|
117
|
+
|
118
|
+
klass.protected_instance_methods(false).each do |m|
|
119
|
+
Do.wrap_method(target, m, :protected)
|
120
|
+
end
|
108
121
|
|
109
|
-
|
122
|
+
klass.private_instance_methods(false).each do |m|
|
123
|
+
Do.wrap_method(target, m, :private)
|
124
|
+
end
|
125
|
+
end
|
110
126
|
end
|
111
127
|
|
112
128
|
# @api private
|
@@ -116,17 +132,18 @@ module Dry
|
|
116
132
|
super
|
117
133
|
|
118
134
|
wrapper = ::Module.new
|
119
|
-
object.singleton_class
|
135
|
+
eigenclass = object.singleton_class
|
136
|
+
eigenclass.prepend(wrapper)
|
120
137
|
object.define_singleton_method(:singleton_method_added) do |method|
|
121
138
|
super(method)
|
122
139
|
|
123
140
|
next if method.equal?(:singleton_method_added)
|
124
141
|
|
125
|
-
Do.
|
126
|
-
|
127
|
-
object.singleton_class.instance_methods(false).each do |m|
|
128
|
-
Do.wrap_method(wrapper, m)
|
142
|
+
visibility = Do.method_visibility(eigenclass, method)
|
143
|
+
Do.wrap_method(wrapper, method, visibility)
|
129
144
|
end
|
145
|
+
|
146
|
+
All.wrap_defined_methods(eigenclass, wrapper)
|
130
147
|
end
|
131
148
|
end
|
132
149
|
|
@@ -134,7 +151,7 @@ module Dry
|
|
134
151
|
end
|
135
152
|
end
|
136
153
|
|
137
|
-
require
|
154
|
+
require "dry/monads/registry"
|
138
155
|
register_mixin(:do, Do::All)
|
139
156
|
end
|
140
157
|
end
|
data/lib/dry/monads/do.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'dry/monads/list'
|
4
|
-
require 'dry/monads/do/mixin'
|
5
|
-
require 'dry/monads/constants'
|
6
|
-
|
7
3
|
module Dry
|
8
4
|
module Monads
|
9
5
|
# An implementation of do-notation.
|
@@ -12,7 +8,11 @@ module Dry
|
|
12
8
|
module Do
|
13
9
|
extend Mixin
|
14
10
|
|
15
|
-
|
11
|
+
VISIBILITY_WORD = {
|
12
|
+
public: "",
|
13
|
+
private: "private ",
|
14
|
+
protected: "protected "
|
15
|
+
}.freeze
|
16
16
|
|
17
17
|
# @api private
|
18
18
|
class Halt < StandardError
|
@@ -26,6 +26,25 @@ module Dry
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
+
# @api private
|
30
|
+
class MethodTracker < ::Module
|
31
|
+
# @api private
|
32
|
+
def initialize(tracked_methods, base, wrapper)
|
33
|
+
module_eval do
|
34
|
+
private
|
35
|
+
|
36
|
+
define_method(:method_added) do |method|
|
37
|
+
super(method)
|
38
|
+
|
39
|
+
if tracked_methods.include?(method)
|
40
|
+
visibility = Do.method_visibility(base, method)
|
41
|
+
Do.wrap_method(wrapper, method, visibility)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
29
48
|
class << self
|
30
49
|
# Generates a module that passes a block to methods
|
31
50
|
# that either unwraps a single-valued monadic value or halts
|
@@ -84,13 +103,11 @@ module Dry
|
|
84
103
|
# @param [Array<Symbol>] methods
|
85
104
|
# @return [Module]
|
86
105
|
def for(*methods)
|
87
|
-
mod = ::Module.new do
|
88
|
-
methods.each { |method_name| Do.wrap_method(self, method_name) }
|
89
|
-
end
|
90
|
-
|
91
106
|
::Module.new do
|
92
|
-
singleton_class.
|
107
|
+
singleton_class.define_method(:included) do |base|
|
108
|
+
mod = ::Module.new
|
93
109
|
base.prepend(mod)
|
110
|
+
base.extend(MethodTracker.new(methods, base, mod))
|
94
111
|
end
|
95
112
|
end
|
96
113
|
end
|
@@ -100,23 +117,34 @@ module Dry
|
|
100
117
|
super
|
101
118
|
|
102
119
|
# Actually mixes in Do::All
|
103
|
-
require
|
120
|
+
require "dry/monads/do/all"
|
104
121
|
base.include All
|
105
122
|
end
|
106
123
|
|
107
124
|
# @api private
|
108
|
-
def wrap_method(target,
|
125
|
+
def wrap_method(target, method, visibility)
|
109
126
|
target.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
110
|
-
def #{
|
111
|
-
if block_given?
|
112
|
-
super
|
113
|
-
else
|
114
|
-
Do.() { super { |*ms| Do.bind(ms) } }
|
115
|
-
end
|
116
|
-
end
|
127
|
+
#{VISIBILITY_WORD[visibility]} def #{method}(...) # private def create_acccount(...)
|
128
|
+
if block_given? # if block_given?
|
129
|
+
super # super
|
130
|
+
else # else
|
131
|
+
Do.() { super { |*ms| Do.bind(ms) } } # Do.() { super { |*ms| Do.bind(ms) } }
|
132
|
+
end # end
|
133
|
+
end # end
|
117
134
|
RUBY
|
118
135
|
end
|
119
136
|
|
137
|
+
# @api private
|
138
|
+
def method_visibility(mod, method)
|
139
|
+
if mod.public_method_defined?(method)
|
140
|
+
:public
|
141
|
+
elsif mod.private_method_defined?(method)
|
142
|
+
:private
|
143
|
+
else
|
144
|
+
:protected
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
120
148
|
# @api private
|
121
149
|
def coerce_to_monad(monads)
|
122
150
|
return monads if monads.size != 1
|
@@ -135,7 +163,7 @@ module Dry
|
|
135
163
|
|
136
164
|
# @api private
|
137
165
|
def halt(result)
|
138
|
-
raise Halt.new(result),
|
166
|
+
raise Halt.new(result), "", []
|
139
167
|
end
|
140
168
|
end
|
141
169
|
end
|
data/lib/dry/monads/errors.rb
CHANGED
@@ -3,21 +3,24 @@
|
|
3
3
|
module Dry
|
4
4
|
module Monads
|
5
5
|
# An unsuccessful result of extracting a value from a monad.
|
6
|
-
class UnwrapError < StandardError
|
7
|
-
|
8
|
-
|
6
|
+
class UnwrapError < ::StandardError
|
7
|
+
attr_reader :receiver
|
8
|
+
|
9
|
+
def initialize(receiver)
|
10
|
+
@receiver = receiver
|
11
|
+
super("value! was called on #{receiver.inspect}")
|
9
12
|
end
|
10
13
|
end
|
11
14
|
|
12
15
|
# An error thrown on returning a Failure of unknown type.
|
13
|
-
class InvalidFailureTypeError < StandardError
|
16
|
+
class InvalidFailureTypeError < ::StandardError
|
14
17
|
def initialize(failure)
|
15
18
|
super("Cannot create Failure from #{failure.inspect}, it doesn't meet the constraints")
|
16
19
|
end
|
17
20
|
end
|
18
21
|
|
19
22
|
# Improper use of None
|
20
|
-
class ConstructorNotAppliedError < NoMethodError
|
23
|
+
class ConstructorNotAppliedError < ::NoMethodError
|
21
24
|
def initialize(method_name, constructor_name)
|
22
25
|
super(
|
23
26
|
"For calling .#{method_name} on #{constructor_name}() build a value "\
|
data/lib/dry/monads/lazy.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
|
5
|
-
require 'dry/monads/task'
|
3
|
+
require "concurrent/promise"
|
6
4
|
|
7
5
|
module Dry
|
8
6
|
module Monads
|
@@ -11,6 +9,8 @@ module Dry
|
|
11
9
|
# computation is evaluated not more than once (compare with the built-in
|
12
10
|
# lazy assignement ||= which does not guarantee this).
|
13
11
|
class Lazy < Task
|
12
|
+
extend ::Dry::Core::Deprecations[:"dry-monads"]
|
13
|
+
|
14
14
|
class << self
|
15
15
|
# @private
|
16
16
|
def new(promise = nil, &block)
|
@@ -41,6 +41,14 @@ module Dry
|
|
41
41
|
self
|
42
42
|
end
|
43
43
|
|
44
|
+
# @return [Boolean]
|
45
|
+
def evaluated?
|
46
|
+
@promise.complete?
|
47
|
+
end
|
48
|
+
deprecate :complete?, :evaluated?
|
49
|
+
|
50
|
+
undef_method :wait
|
51
|
+
|
44
52
|
# @return [String]
|
45
53
|
def to_s
|
46
54
|
state = case promise.state
|
@@ -49,7 +57,7 @@ module Dry
|
|
49
57
|
when :rejected
|
50
58
|
"!#{promise.reason.inspect}"
|
51
59
|
else
|
52
|
-
|
60
|
+
"?"
|
53
61
|
end
|
54
62
|
|
55
63
|
"Lazy(#{state})"
|
@@ -80,7 +88,7 @@ module Dry
|
|
80
88
|
end
|
81
89
|
end
|
82
90
|
|
83
|
-
require
|
91
|
+
require "dry/monads/registry"
|
84
92
|
register_mixin(:lazy, Lazy::Mixin)
|
85
93
|
end
|
86
94
|
end
|
data/lib/dry/monads/list.rb
CHANGED
@@ -1,15 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'dry/equalizer'
|
4
|
-
|
5
|
-
require 'dry/monads/maybe'
|
6
|
-
require 'dry/monads/task'
|
7
|
-
require 'dry/monads/result'
|
8
|
-
require 'dry/monads/try'
|
9
|
-
require 'dry/monads/validated'
|
10
|
-
require 'dry/monads/transformer'
|
11
|
-
require 'dry/monads/curry'
|
12
|
-
|
13
3
|
module Dry
|
14
4
|
module Monads
|
15
5
|
# The List monad.
|
@@ -83,7 +73,7 @@ module Dry
|
|
83
73
|
end
|
84
74
|
end
|
85
75
|
|
86
|
-
extend Dry::Core::Deprecations[:
|
76
|
+
extend Dry::Core::Deprecations[:"dry-monads"]
|
87
77
|
|
88
78
|
include Dry::Equalizer(:value, :type)
|
89
79
|
include Transformer
|
@@ -110,10 +100,10 @@ module Dry
|
|
110
100
|
# @return [List]
|
111
101
|
def bind(*args)
|
112
102
|
if block_given?
|
113
|
-
List.coerce(value.map {
|
103
|
+
List.coerce(value.map { yield(_1, *args) }.reduce([], &:+))
|
114
104
|
else
|
115
105
|
obj, *rest = args
|
116
|
-
List.coerce(value.map {
|
106
|
+
List.coerce(value.map { obj.(_1, *rest) }.reduce([], &:+))
|
117
107
|
end
|
118
108
|
end
|
119
109
|
|
@@ -128,22 +118,22 @@ module Dry
|
|
128
118
|
# @return [List]
|
129
119
|
def fmap(*args)
|
130
120
|
if block_given?
|
131
|
-
List.new(value.map {
|
121
|
+
List.new(value.map { yield(_1, *args) })
|
132
122
|
else
|
133
123
|
obj, *rest = args
|
134
|
-
List.new(value.map {
|
124
|
+
List.new(value.map { obj.(_1, *rest) })
|
135
125
|
end
|
136
126
|
end
|
137
127
|
|
138
128
|
# Maps a block over the list. Acts as `Array#map`.
|
139
|
-
#
|
129
|
+
# If called without a block, this method returns an enumerator, not a List
|
140
130
|
#
|
141
131
|
# @return [List,Enumerator]
|
142
132
|
def map(&block)
|
143
|
-
if
|
133
|
+
if block_given?
|
144
134
|
fmap(block)
|
145
135
|
else
|
146
|
-
value.map
|
136
|
+
value.map
|
147
137
|
end
|
148
138
|
end
|
149
139
|
|
@@ -165,7 +155,7 @@ module Dry
|
|
165
155
|
#
|
166
156
|
# @return [String]
|
167
157
|
def inspect
|
168
|
-
type_ann = typed? ? "<#{type.name.split(
|
158
|
+
type_ann = typed? ? "<#{type.name.split("::").last}>" : ""
|
169
159
|
"List#{type_ann}#{value.inspect}"
|
170
160
|
end
|
171
161
|
alias_method :to_s, :inspect
|
@@ -192,8 +182,8 @@ module Dry
|
|
192
182
|
#
|
193
183
|
# @param initial [Object] Initial value
|
194
184
|
# @return [Object]
|
195
|
-
def fold_left(initial)
|
196
|
-
value.reduce(initial
|
185
|
+
def fold_left(initial, &block)
|
186
|
+
value.reduce(initial, &block)
|
197
187
|
end
|
198
188
|
alias_method :foldl, :fold_left
|
199
189
|
alias_method :reduce, :fold_left
|
@@ -224,8 +214,8 @@ module Dry
|
|
224
214
|
# Filters elements with a block
|
225
215
|
#
|
226
216
|
# @return [List]
|
227
|
-
def filter
|
228
|
-
coerce(value.select
|
217
|
+
def filter(&block)
|
218
|
+
coerce(value.select(&block))
|
229
219
|
end
|
230
220
|
alias_method :select, :filter
|
231
221
|
|
@@ -265,12 +255,12 @@ module Dry
|
|
265
255
|
def typed(type = nil)
|
266
256
|
if type.nil?
|
267
257
|
if size.zero?
|
268
|
-
raise ArgumentError,
|
258
|
+
raise ArgumentError, "Cannot infer a monad for an empty list"
|
269
259
|
else
|
270
260
|
self.class.warn(
|
271
|
-
|
261
|
+
"Automatic monad inference is deprecated, pass a type explicitly "\
|
272
262
|
"or use a predefined constant, e.g. List::Result\n"\
|
273
|
-
"#{caller.find {
|
263
|
+
"#{caller.find { _1 !~ %r{(lib/dry/monads)|(gems)} }}"
|
274
264
|
)
|
275
265
|
self.class.new(value, value[0].monad)
|
276
266
|
end
|
@@ -298,7 +288,7 @@ module Dry
|
|
298
288
|
# @return [Monad] Result is a monadic value
|
299
289
|
def traverse(proc = nil, &block)
|
300
290
|
unless typed?
|
301
|
-
raise StandardError,
|
291
|
+
raise StandardError, "Cannot traverse an untyped list"
|
302
292
|
end
|
303
293
|
|
304
294
|
cons = type.pure { |list, i| list + List.pure(i) }
|
@@ -315,9 +305,9 @@ module Dry
|
|
315
305
|
#
|
316
306
|
# @param list [List]
|
317
307
|
# @return [List]
|
318
|
-
def apply(list = Undefined)
|
319
|
-
v = Undefined.default(list)
|
320
|
-
fmap(Curry).bind { |f| v.fmap {
|
308
|
+
def apply(list = Undefined, &block)
|
309
|
+
v = Undefined.default(list, &block)
|
310
|
+
fmap(Curry).bind { |f| v.fmap { f.(_1) } }
|
321
311
|
end
|
322
312
|
|
323
313
|
# Returns the List monad.
|
@@ -329,7 +319,7 @@ module Dry
|
|
329
319
|
|
330
320
|
# Returns self.
|
331
321
|
#
|
332
|
-
# @return [
|
322
|
+
# @return [List]
|
333
323
|
def to_monad
|
334
324
|
self
|
335
325
|
end
|
@@ -345,7 +335,7 @@ module Dry
|
|
345
335
|
# Some(n / divisor)
|
346
336
|
# end
|
347
337
|
# end
|
348
|
-
# # => List[
|
338
|
+
# # => List[2, 4]
|
349
339
|
#
|
350
340
|
# @example without block
|
351
341
|
# List[Some(5), None(), Some(3)].collect.map { |x| x * 2 }
|
@@ -362,7 +352,7 @@ module Dry
|
|
362
352
|
List.new(collected)
|
363
353
|
else
|
364
354
|
Enumerator.new do |g|
|
365
|
-
value.each {
|
355
|
+
value.each { g << _1.value! if _1.some? }
|
366
356
|
end
|
367
357
|
end
|
368
358
|
end
|
@@ -422,10 +412,10 @@ module Dry
|
|
422
412
|
# List of results
|
423
413
|
Result = ListBuilder[Result]
|
424
414
|
|
425
|
-
# List of
|
415
|
+
# List of maybes
|
426
416
|
Maybe = ListBuilder[Maybe]
|
427
417
|
|
428
|
-
# List of
|
418
|
+
# List of tries
|
429
419
|
Try = ListBuilder[Try]
|
430
420
|
|
431
421
|
# List of validation results
|
@@ -449,9 +439,9 @@ module Dry
|
|
449
439
|
end
|
450
440
|
end
|
451
441
|
|
452
|
-
require
|
442
|
+
require "dry/monads/registry"
|
453
443
|
register_mixin(:list, List::Mixin)
|
454
444
|
end
|
455
445
|
end
|
456
446
|
|
457
|
-
require
|
447
|
+
require "dry/monads/traverse"
|
data/lib/dry/monads/maybe.rb
CHANGED
@@ -1,13 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'dry/equalizer'
|
4
|
-
require 'dry/core/deprecations'
|
5
|
-
|
6
|
-
require 'dry/monads/right_biased'
|
7
|
-
require 'dry/monads/transformer'
|
8
|
-
require 'dry/monads/unit'
|
9
|
-
require 'dry/monads/constants'
|
10
|
-
|
11
3
|
module Dry
|
12
4
|
module Monads
|
13
5
|
# Represents a value which can exist or not, i.e. it could be nil.
|
@@ -15,9 +7,13 @@ module Dry
|
|
15
7
|
# @api public
|
16
8
|
class Maybe
|
17
9
|
include Transformer
|
10
|
+
extend Core::ClassAttributes
|
11
|
+
|
12
|
+
defines :warn_on_implicit_nil_coercion
|
13
|
+
warn_on_implicit_nil_coercion true
|
18
14
|
|
19
15
|
class << self
|
20
|
-
extend Core::Deprecations[:
|
16
|
+
extend Core::Deprecations[:"dry-monads"]
|
21
17
|
|
22
18
|
# Wraps the given value with into a Maybe object.
|
23
19
|
#
|
@@ -105,7 +101,9 @@ module Dry
|
|
105
101
|
end
|
106
102
|
|
107
103
|
def initialize(value = Undefined)
|
108
|
-
raise ArgumentError,
|
104
|
+
raise ArgumentError, "nil cannot be some" if value.nil?
|
105
|
+
|
106
|
+
super()
|
109
107
|
|
110
108
|
@value = Undefined.default(value, Unit)
|
111
109
|
end
|
@@ -120,15 +118,66 @@ module Dry
|
|
120
118
|
# @param args [Array<Object>] arguments will be transparently passed through to #bind
|
121
119
|
# @return [Maybe::Some, Maybe::None] Wrapped result, i.e. nil will be mapped to None,
|
122
120
|
# other values will be wrapped with Some
|
123
|
-
def fmap(
|
124
|
-
|
121
|
+
def fmap(...)
|
122
|
+
next_value = bind(...)
|
123
|
+
|
124
|
+
if next_value.nil?
|
125
|
+
if self.class.warn_on_implicit_nil_coercion
|
126
|
+
Core::Deprecations.warn(
|
127
|
+
"Block passed to Some#fmap returned `nil` and was chained to None. "\
|
128
|
+
"This is literally an unlawful behavior and it will not be supported in "\
|
129
|
+
"dry-monads 2. \nPlease, replace `.fmap` with `.maybe` in places where you "\
|
130
|
+
"expect `nil` as block result.\n"\
|
131
|
+
"You can opt out of these warnings with\n"\
|
132
|
+
"Dry::Monads::Maybe.warn_on_implicit_nil_coercion false",
|
133
|
+
uplevel: 0,
|
134
|
+
tag: :"dry-monads"
|
135
|
+
)
|
136
|
+
end
|
137
|
+
Monads.None()
|
138
|
+
else
|
139
|
+
Some.new(next_value)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Does the same thing as #bind except it also wraps the value
|
144
|
+
# in an instance of the Maybe monad. This allows for easier
|
145
|
+
# chaining of calls.
|
146
|
+
#
|
147
|
+
# @example
|
148
|
+
# Dry::Monads.Some(4).maybe(&:succ).maybe(->(n) { n**2 }) # => Some(25)
|
149
|
+
# Dry::Monads.Some(4).maybe(&:succ).maybe(->(_) { nil }) # => None()
|
150
|
+
#
|
151
|
+
# @param args [Array<Object>] arguments will be transparently passed through to #bind
|
152
|
+
# @return [Maybe::Some, Maybe::None] Wrapped result, i.e. nil will be mapped to None,
|
153
|
+
# other values will be wrapped with Some
|
154
|
+
def maybe(...)
|
155
|
+
Maybe.coerce(bind(...))
|
156
|
+
end
|
157
|
+
|
158
|
+
# Accepts a block and runs it against the wrapped value.
|
159
|
+
# If the block returns a trurhy value the result is self,
|
160
|
+
# otherwise None. If no block is given, the value serves
|
161
|
+
# and its result.
|
162
|
+
#
|
163
|
+
# @param with [#call] positional block
|
164
|
+
# @param block [Proc] block
|
165
|
+
#
|
166
|
+
# @return [Maybe::None, Maybe::Some]
|
167
|
+
def filter(with = Undefined, &block)
|
168
|
+
block = Undefined.default(with, block || IDENTITY)
|
169
|
+
|
170
|
+
if block.(@value)
|
171
|
+
self
|
172
|
+
else
|
173
|
+
Monads.None()
|
174
|
+
end
|
125
175
|
end
|
126
|
-
alias_method :maybe, :fmap
|
127
176
|
|
128
177
|
# @return [String]
|
129
178
|
def to_s
|
130
179
|
if Unit.equal?(@value)
|
131
|
-
|
180
|
+
"Some()"
|
132
181
|
else
|
133
182
|
"Some(#{@value.inspect})"
|
134
183
|
end
|
@@ -143,10 +192,10 @@ module Dry
|
|
143
192
|
include RightBiased::Left
|
144
193
|
|
145
194
|
@instance = new.freeze
|
146
|
-
singleton_class.
|
195
|
+
singleton_class.attr_reader(:instance)
|
147
196
|
|
148
197
|
# @api private
|
149
|
-
def self.method_missing(m, *)
|
198
|
+
def self.method_missing(m, *) # rubocop:disable Style/MissingRespondToMissing
|
150
199
|
if (instance.methods(true) - methods(true)).include?(m)
|
151
200
|
raise ConstructorNotAppliedError.new(m, :None)
|
152
201
|
else
|
@@ -161,6 +210,7 @@ module Dry
|
|
161
210
|
attr_reader :trace
|
162
211
|
|
163
212
|
def initialize(trace = RightBiased::Left.trace_caller)
|
213
|
+
super()
|
164
214
|
@trace = trace
|
165
215
|
end
|
166
216
|
|
@@ -196,13 +246,13 @@ module Dry
|
|
196
246
|
# @param args [Array<Object>] arguments will be passed to the underlying `#or` call
|
197
247
|
# @return [Maybe::Some, Maybe::None] Lifted `#or` result, i.e. nil will be mapped to None,
|
198
248
|
# other values will be wrapped with Some
|
199
|
-
def or_fmap(
|
200
|
-
Maybe.coerce(self.or(
|
249
|
+
def or_fmap(...)
|
250
|
+
Maybe.coerce(self.or(...))
|
201
251
|
end
|
202
252
|
|
203
253
|
# @return [String]
|
204
254
|
def to_s
|
205
|
-
|
255
|
+
"None"
|
206
256
|
end
|
207
257
|
alias_method :inspect, :to_s
|
208
258
|
|
@@ -230,6 +280,13 @@ module Dry
|
|
230
280
|
def deconstruct
|
231
281
|
EMPTY_ARRAY
|
232
282
|
end
|
283
|
+
|
284
|
+
# @see Maybe::Some#filter
|
285
|
+
#
|
286
|
+
# @return [Maybe::None]
|
287
|
+
def filter(_ = Undefined)
|
288
|
+
self
|
289
|
+
end
|
233
290
|
end
|
234
291
|
|
235
292
|
# A module that can be included for easier access to Maybe monads.
|
@@ -301,9 +358,9 @@ module Dry
|
|
301
358
|
# None values are removed
|
302
359
|
#
|
303
360
|
# @example
|
304
|
-
# Maybe::Hash.filter(foo: Some(1), bar: Some(2)) # =>
|
305
|
-
# Maybe::Hash.filter(foo: Some(1), bar: None()) # =>
|
306
|
-
# Maybe::Hash.filter(foo: None(), bar: Some(2)) # =>
|
361
|
+
# Maybe::Hash.filter(foo: Some(1), bar: Some(2)) # => { foo: 1, bar: 2 }
|
362
|
+
# Maybe::Hash.filter(foo: Some(1), bar: None()) # => { foo: 1 }
|
363
|
+
# Maybe::Hash.filter(foo: None(), bar: Some(2)) # => { bar: 2 }
|
307
364
|
#
|
308
365
|
# @param hash [::Hash<Object,Maybe>]
|
309
366
|
# @return [::Hash]
|
@@ -325,10 +382,12 @@ module Dry
|
|
325
382
|
|
326
383
|
class Result
|
327
384
|
class Success < Result
|
328
|
-
|
385
|
+
extend Core::Deprecations[:"dry-monads"]
|
386
|
+
|
387
|
+
# @return [Maybe]
|
329
388
|
def to_maybe
|
330
|
-
|
331
|
-
Dry::Monads::Maybe(@value)
|
389
|
+
warn "Success(nil) transformed to None" if @value.nil?
|
390
|
+
::Dry::Monads::Maybe(@value)
|
332
391
|
end
|
333
392
|
end
|
334
393
|
|
@@ -389,7 +448,7 @@ module Dry
|
|
389
448
|
end
|
390
449
|
end
|
391
450
|
|
392
|
-
require
|
451
|
+
require "dry/monads/registry"
|
393
452
|
register_mixin(:maybe, Maybe::Mixin)
|
394
453
|
end
|
395
454
|
end
|