dry-monads 1.3.5 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +152 -80
- data/LICENSE +1 -1
- data/README.md +5 -4
- data/dry-monads.gemspec +31 -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 -54
- 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
|