dry-monads 1.3.2 → 1.4.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 +157 -73
- data/LICENSE +1 -1
- data/README.md +18 -38
- data/dry-monads.gemspec +32 -30
- data/lib/dry-monads.rb +3 -1
- data/lib/dry/monads.rb +4 -2
- data/lib/dry/monads/all.rb +4 -2
- data/lib/dry/monads/constants.rb +1 -1
- data/lib/dry/monads/conversion_stubs.rb +2 -0
- data/lib/dry/monads/curry.rb +2 -0
- data/lib/dry/monads/do.rb +55 -17
- data/lib/dry/monads/do/all.rb +39 -17
- data/lib/dry/monads/do/mixin.rb +2 -0
- data/lib/dry/monads/either.rb +9 -7
- data/lib/dry/monads/errors.rb +8 -3
- data/lib/dry/monads/lazy.rb +19 -6
- data/lib/dry/monads/list.rb +31 -30
- data/lib/dry/monads/maybe.rb +90 -19
- data/lib/dry/monads/registry.rb +15 -12
- data/lib/dry/monads/result.rb +42 -15
- data/lib/dry/monads/result/fixed.rb +35 -24
- data/lib/dry/monads/right_biased.rb +45 -24
- data/lib/dry/monads/task.rb +25 -22
- data/lib/dry/monads/transformer.rb +4 -1
- data/lib/dry/monads/traverse.rb +9 -1
- data/lib/dry/monads/try.rb +51 -13
- data/lib/dry/monads/unit.rb +6 -2
- data/lib/dry/monads/validated.rb +27 -20
- data/lib/dry/monads/version.rb +3 -1
- data/lib/json/add/dry/monads/maybe.rb +4 -3
- metadata +27 -75
- 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 -34
- data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
- data/.github/workflows/ci.yml +0 -74
- data/.github/workflows/docsite.yml +0 -34
- data/.github/workflows/sync_configs.yml +0 -34
- data/.gitignore +0 -10
- data/.rspec +0 -4
- data/.rubocop.yml +0 -89
- data/.yardopts +0 -4
- data/CODE_OF_CONDUCT.md +0 -13
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -23
- data/Rakefile +0 -6
- data/bin/console +0 -16
- 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/log/.gitkeep +0 -0
data/lib/dry/monads/errors.rb
CHANGED
@@ -1,16 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Monads
|
3
5
|
# An unsuccessful result of extracting a value from a monad.
|
4
6
|
class UnwrapError < StandardError
|
5
|
-
|
6
|
-
|
7
|
+
attr_reader :receiver
|
8
|
+
|
9
|
+
def initialize(receiver)
|
10
|
+
@receiver = receiver
|
11
|
+
super("value! was called on #{receiver.inspect}")
|
7
12
|
end
|
8
13
|
end
|
9
14
|
|
10
15
|
# An error thrown on returning a Failure of unknown type.
|
11
16
|
class InvalidFailureTypeError < StandardError
|
12
17
|
def initialize(failure)
|
13
|
-
super("Cannot create Failure from #{
|
18
|
+
super("Cannot create Failure from #{failure.inspect}, it doesn't meet the constraints")
|
14
19
|
end
|
15
20
|
end
|
16
21
|
|
data/lib/dry/monads/lazy.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "concurrent/promise"
|
4
|
+
|
5
|
+
require "dry/core/deprecations"
|
6
|
+
require "dry/monads/task"
|
4
7
|
|
5
8
|
module Dry
|
6
9
|
module Monads
|
@@ -9,6 +12,8 @@ module Dry
|
|
9
12
|
# computation is evaluated not more than once (compare with the built-in
|
10
13
|
# lazy assignement ||= which does not guarantee this).
|
11
14
|
class Lazy < Task
|
15
|
+
extend ::Dry::Core::Deprecations[:"dry-monads"]
|
16
|
+
|
12
17
|
class << self
|
13
18
|
# @private
|
14
19
|
def new(promise = nil, &block)
|
@@ -39,18 +44,26 @@ module Dry
|
|
39
44
|
self
|
40
45
|
end
|
41
46
|
|
47
|
+
# @return [Boolean]
|
48
|
+
def evaluated?
|
49
|
+
@promise.complete?
|
50
|
+
end
|
51
|
+
deprecate :complete?, :evaluated?
|
52
|
+
|
53
|
+
undef_method :wait
|
54
|
+
|
42
55
|
# @return [String]
|
43
56
|
def to_s
|
44
57
|
state = case promise.state
|
45
58
|
when :fulfilled
|
46
59
|
value!.inspect
|
47
60
|
when :rejected
|
48
|
-
"!#{
|
61
|
+
"!#{promise.reason.inspect}"
|
49
62
|
else
|
50
|
-
|
63
|
+
"?"
|
51
64
|
end
|
52
65
|
|
53
|
-
"Lazy(#{
|
66
|
+
"Lazy(#{state})"
|
54
67
|
end
|
55
68
|
alias_method :inspect, :to_s
|
56
69
|
|
@@ -78,7 +91,7 @@ module Dry
|
|
78
91
|
end
|
79
92
|
end
|
80
93
|
|
81
|
-
require
|
94
|
+
require "dry/monads/registry"
|
82
95
|
register_mixin(:lazy, Lazy::Mixin)
|
83
96
|
end
|
84
97
|
end
|
data/lib/dry/monads/list.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
3
|
+
require "dry/core/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"
|
10
12
|
|
11
13
|
module Dry
|
12
14
|
module Monads
|
@@ -81,7 +83,7 @@ module Dry
|
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
84
|
-
extend Dry::Core::Deprecations[:
|
86
|
+
extend Dry::Core::Deprecations[:"dry-monads"]
|
85
87
|
|
86
88
|
include Dry::Equalizer(:value, :type)
|
87
89
|
include Transformer
|
@@ -134,14 +136,14 @@ module Dry
|
|
134
136
|
end
|
135
137
|
|
136
138
|
# Maps a block over the list. Acts as `Array#map`.
|
137
|
-
#
|
139
|
+
# If called without a block, this method returns an enumerator, not a List
|
138
140
|
#
|
139
141
|
# @return [List,Enumerator]
|
140
142
|
def map(&block)
|
141
|
-
if
|
143
|
+
if block_given?
|
142
144
|
fmap(block)
|
143
145
|
else
|
144
|
-
value.map
|
146
|
+
value.map
|
145
147
|
end
|
146
148
|
end
|
147
149
|
|
@@ -163,8 +165,8 @@ module Dry
|
|
163
165
|
#
|
164
166
|
# @return [String]
|
165
167
|
def inspect
|
166
|
-
type_ann = typed? ? "<#{
|
167
|
-
"List#{
|
168
|
+
type_ann = typed? ? "<#{type.name.split("::").last}>" : ""
|
169
|
+
"List#{type_ann}#{value.inspect}"
|
168
170
|
end
|
169
171
|
alias_method :to_s, :inspect
|
170
172
|
|
@@ -190,8 +192,8 @@ module Dry
|
|
190
192
|
#
|
191
193
|
# @param initial [Object] Initial value
|
192
194
|
# @return [Object]
|
193
|
-
def fold_left(initial)
|
194
|
-
value.reduce(initial
|
195
|
+
def fold_left(initial, &block)
|
196
|
+
value.reduce(initial, &block)
|
195
197
|
end
|
196
198
|
alias_method :foldl, :fold_left
|
197
199
|
alias_method :reduce, :fold_left
|
@@ -222,8 +224,8 @@ module Dry
|
|
222
224
|
# Filters elements with a block
|
223
225
|
#
|
224
226
|
# @return [List]
|
225
|
-
def filter
|
226
|
-
coerce(value.select
|
227
|
+
def filter(&block)
|
228
|
+
coerce(value.select(&block))
|
227
229
|
end
|
228
230
|
alias_method :select, :filter
|
229
231
|
|
@@ -303,9 +305,9 @@ module Dry
|
|
303
305
|
with = proc || block || Traverse[type]
|
304
306
|
|
305
307
|
foldl(type.pure(EMPTY)) do |acc, el|
|
306
|
-
cons
|
307
|
-
apply(acc)
|
308
|
-
apply { with.(el) }
|
308
|
+
cons
|
309
|
+
.apply(acc)
|
310
|
+
.apply { with.(el) }
|
309
311
|
end
|
310
312
|
end
|
311
313
|
|
@@ -313,8 +315,8 @@ module Dry
|
|
313
315
|
#
|
314
316
|
# @param list [List]
|
315
317
|
# @return [List]
|
316
|
-
def apply(list = Undefined)
|
317
|
-
v = Undefined.default(list)
|
318
|
+
def apply(list = Undefined, &block)
|
319
|
+
v = Undefined.default(list, &block)
|
318
320
|
fmap(Curry).bind { |f| v.fmap { |x| f.(x) } }
|
319
321
|
end
|
320
322
|
|
@@ -327,7 +329,7 @@ module Dry
|
|
327
329
|
|
328
330
|
# Returns self.
|
329
331
|
#
|
330
|
-
# @return [
|
332
|
+
# @return [List]
|
331
333
|
def to_monad
|
332
334
|
self
|
333
335
|
end
|
@@ -343,7 +345,7 @@ module Dry
|
|
343
345
|
# Some(n / divisor)
|
344
346
|
# end
|
345
347
|
# end
|
346
|
-
# # => List[
|
348
|
+
# # => List[2, 4]
|
347
349
|
#
|
348
350
|
# @example without block
|
349
351
|
# List[Some(5), None(), Some(3)].collect.map { |x| x * 2 }
|
@@ -420,10 +422,10 @@ module Dry
|
|
420
422
|
# List of results
|
421
423
|
Result = ListBuilder[Result]
|
422
424
|
|
423
|
-
# List of
|
425
|
+
# List of maybes
|
424
426
|
Maybe = ListBuilder[Maybe]
|
425
427
|
|
426
|
-
# List of
|
428
|
+
# List of tries
|
427
429
|
Try = ListBuilder[Try]
|
428
430
|
|
429
431
|
# List of validation results
|
@@ -433,7 +435,6 @@ module Dry
|
|
433
435
|
#
|
434
436
|
# @api public
|
435
437
|
module Mixin
|
436
|
-
|
437
438
|
# @see Dry::Monads::List
|
438
439
|
List = List
|
439
440
|
|
@@ -448,9 +449,9 @@ module Dry
|
|
448
449
|
end
|
449
450
|
end
|
450
451
|
|
451
|
-
require
|
452
|
+
require "dry/monads/registry"
|
452
453
|
register_mixin(:list, List::Mixin)
|
453
454
|
end
|
454
455
|
end
|
455
456
|
|
456
|
-
require
|
457
|
+
require "dry/monads/traverse"
|
data/lib/dry/monads/maybe.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
|
2
|
-
require 'dry/core/deprecations'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
require "dry/core/deprecations"
|
5
|
+
require "dry/core/class_attributes"
|
6
|
+
|
7
|
+
require "dry/monads/right_biased"
|
8
|
+
require "dry/monads/transformer"
|
9
|
+
require "dry/monads/unit"
|
10
|
+
require "dry/monads/constants"
|
8
11
|
|
9
12
|
module Dry
|
10
13
|
module Monads
|
@@ -13,9 +16,13 @@ module Dry
|
|
13
16
|
# @api public
|
14
17
|
class Maybe
|
15
18
|
include Transformer
|
19
|
+
extend Core::ClassAttributes
|
20
|
+
|
21
|
+
defines :warn_on_implicit_nil_coercion
|
22
|
+
warn_on_implicit_nil_coercion true
|
16
23
|
|
17
24
|
class << self
|
18
|
-
extend Core::Deprecations[:
|
25
|
+
extend Core::Deprecations[:"dry-monads"]
|
19
26
|
|
20
27
|
# Wraps the given value with into a Maybe object.
|
21
28
|
#
|
@@ -103,7 +110,10 @@ module Dry
|
|
103
110
|
end
|
104
111
|
|
105
112
|
def initialize(value = Undefined)
|
106
|
-
raise ArgumentError,
|
113
|
+
raise ArgumentError, "nil cannot be some" if value.nil?
|
114
|
+
|
115
|
+
super()
|
116
|
+
|
107
117
|
@value = Undefined.default(value, Unit)
|
108
118
|
end
|
109
119
|
|
@@ -118,14 +128,65 @@ module Dry
|
|
118
128
|
# @return [Maybe::Some, Maybe::None] Wrapped result, i.e. nil will be mapped to None,
|
119
129
|
# other values will be wrapped with Some
|
120
130
|
def fmap(*args, &block)
|
131
|
+
next_value = bind(*args, &block)
|
132
|
+
|
133
|
+
if next_value.nil?
|
134
|
+
if self.class.warn_on_implicit_nil_coercion
|
135
|
+
Core::Deprecations.warn(
|
136
|
+
"Block passed to Some#fmap returned `nil` and was chained to None. "\
|
137
|
+
"This is literally an unlawful behavior and it will not be supported in "\
|
138
|
+
"dry-monads 2. \nPlease, replace `.fmap` with `.maybe` in places where you "\
|
139
|
+
"expect `nil` as block result.\n"\
|
140
|
+
"You can opt out of these warnings with\n"\
|
141
|
+
"Dry::Monads::Maybe.warn_on_implicit_nil_coercion false",
|
142
|
+
uplevel: 0,
|
143
|
+
tag: :'dry-monads'
|
144
|
+
)
|
145
|
+
end
|
146
|
+
Monads.None()
|
147
|
+
else
|
148
|
+
Some.new(next_value)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Does the same thing as #bind except it also wraps the value
|
153
|
+
# in an instance of the Maybe monad. This allows for easier
|
154
|
+
# chaining of calls.
|
155
|
+
#
|
156
|
+
# @example
|
157
|
+
# Dry::Monads.Some(4).maybe(&:succ).maybe(->(n) { n**2 }) # => Some(25)
|
158
|
+
# Dry::Monads.Some(4).maybe(&:succ).maybe(->(_) { nil }) # => None()
|
159
|
+
#
|
160
|
+
# @param args [Array<Object>] arguments will be transparently passed through to #bind
|
161
|
+
# @return [Maybe::Some, Maybe::None] Wrapped result, i.e. nil will be mapped to None,
|
162
|
+
# other values will be wrapped with Some
|
163
|
+
def maybe(*args, &block)
|
121
164
|
Maybe.coerce(bind(*args, &block))
|
122
165
|
end
|
123
|
-
|
166
|
+
|
167
|
+
# Accepts a block and runs it against the wrapped value.
|
168
|
+
# If the block returns a trurhy value the result is self,
|
169
|
+
# otherwise None. If no block is given, the value serves
|
170
|
+
# and its result.
|
171
|
+
#
|
172
|
+
# @param with [#call] positional block
|
173
|
+
# @param block [Proc] block
|
174
|
+
#
|
175
|
+
# @return [Maybe::None, Maybe::Some]
|
176
|
+
def filter(with = Undefined, &block)
|
177
|
+
block = Undefined.default(with, block || IDENTITY)
|
178
|
+
|
179
|
+
if block.(@value)
|
180
|
+
self
|
181
|
+
else
|
182
|
+
Monads.None()
|
183
|
+
end
|
184
|
+
end
|
124
185
|
|
125
186
|
# @return [String]
|
126
187
|
def to_s
|
127
188
|
if Unit.equal?(@value)
|
128
|
-
|
189
|
+
"Some()"
|
129
190
|
else
|
130
191
|
"Some(#{@value.inspect})"
|
131
192
|
end
|
@@ -143,7 +204,7 @@ module Dry
|
|
143
204
|
singleton_class.send(:attr_reader, :instance)
|
144
205
|
|
145
206
|
# @api private
|
146
|
-
def self.method_missing(m, *)
|
207
|
+
def self.method_missing(m, *) # rubocop:disable Style/MissingRespondToMissing
|
147
208
|
if (instance.methods(true) - methods(true)).include?(m)
|
148
209
|
raise ConstructorNotAppliedError.new(m, :None)
|
149
210
|
else
|
@@ -158,6 +219,7 @@ module Dry
|
|
158
219
|
attr_reader :trace
|
159
220
|
|
160
221
|
def initialize(trace = RightBiased::Left.trace_caller)
|
222
|
+
super()
|
161
223
|
@trace = trace
|
162
224
|
end
|
163
225
|
|
@@ -199,7 +261,7 @@ module Dry
|
|
199
261
|
|
200
262
|
# @return [String]
|
201
263
|
def to_s
|
202
|
-
|
264
|
+
"None"
|
203
265
|
end
|
204
266
|
alias_method :inspect, :to_s
|
205
267
|
|
@@ -227,6 +289,13 @@ module Dry
|
|
227
289
|
def deconstruct
|
228
290
|
EMPTY_ARRAY
|
229
291
|
end
|
292
|
+
|
293
|
+
# @see Maybe::Some#filter
|
294
|
+
#
|
295
|
+
# @return [Maybe::None]
|
296
|
+
def filter(_ = Undefined)
|
297
|
+
self
|
298
|
+
end
|
230
299
|
end
|
231
300
|
|
232
301
|
# A module that can be included for easier access to Maybe monads.
|
@@ -298,9 +367,9 @@ module Dry
|
|
298
367
|
# None values are removed
|
299
368
|
#
|
300
369
|
# @example
|
301
|
-
# Maybe::Hash.filter(foo: Some(1), bar: Some(2)) # =>
|
302
|
-
# Maybe::Hash.filter(foo: Some(1), bar: None()) # =>
|
303
|
-
# Maybe::Hash.filter(foo: None(), bar: Some(2)) # =>
|
370
|
+
# Maybe::Hash.filter(foo: Some(1), bar: Some(2)) # => { foo: 1, bar: 2 }
|
371
|
+
# Maybe::Hash.filter(foo: Some(1), bar: None()) # => { foo: 1 }
|
372
|
+
# Maybe::Hash.filter(foo: None(), bar: Some(2)) # => { bar: 2 }
|
304
373
|
#
|
305
374
|
# @param hash [::Hash<Object,Maybe>]
|
306
375
|
# @return [::Hash]
|
@@ -322,10 +391,12 @@ module Dry
|
|
322
391
|
|
323
392
|
class Result
|
324
393
|
class Success < Result
|
325
|
-
|
394
|
+
extend Core::Deprecations[:"dry-monads"]
|
395
|
+
|
396
|
+
# @return [Maybe]
|
326
397
|
def to_maybe
|
327
|
-
|
328
|
-
Dry::Monads::Maybe(@value)
|
398
|
+
warn "Success(nil) transformed to None" if @value.nil?
|
399
|
+
::Dry::Monads::Maybe(@value)
|
329
400
|
end
|
330
401
|
end
|
331
402
|
|
@@ -386,7 +457,7 @@ module Dry
|
|
386
457
|
end
|
387
458
|
end
|
388
459
|
|
389
|
-
require
|
460
|
+
require "dry/monads/registry"
|
390
461
|
register_mixin(:maybe, Maybe::Mixin)
|
391
462
|
end
|
392
463
|
end
|
data/lib/dry/monads/registry.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent/map"
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
# Common, idiomatic monads for Ruby
|
@@ -8,16 +10,16 @@ module Dry
|
|
8
10
|
@registry = {}
|
9
11
|
@constructors = nil
|
10
12
|
@paths = {
|
11
|
-
do:
|
12
|
-
lazy:
|
13
|
-
list:
|
14
|
-
maybe:
|
15
|
-
task:
|
16
|
-
try:
|
17
|
-
validated:
|
13
|
+
do: "dry/monads/do/all",
|
14
|
+
lazy: "dry/monads/lazy",
|
15
|
+
list: "dry/monads/list",
|
16
|
+
maybe: "dry/monads/maybe",
|
17
|
+
task: "dry/monads/task",
|
18
|
+
try: "dry/monads/try",
|
19
|
+
validated: "dry/monads/validated",
|
18
20
|
result: [
|
19
|
-
|
20
|
-
|
21
|
+
"dry/monads/result",
|
22
|
+
"dry/monads/result/fixed"
|
21
23
|
]
|
22
24
|
}.freeze
|
23
25
|
@mixins = Concurrent::Map.new
|
@@ -36,8 +38,9 @@ module Dry
|
|
36
38
|
# @private
|
37
39
|
def register_mixin(name, mod)
|
38
40
|
if registry.key?(name)
|
39
|
-
raise ArgumentError, "#{
|
41
|
+
raise ArgumentError, "#{name.inspect} is already registered"
|
40
42
|
end
|
43
|
+
|
41
44
|
self.registry = registry.merge(name => mod)
|
42
45
|
end
|
43
46
|
|
@@ -49,7 +52,7 @@ module Dry
|
|
49
52
|
# @private
|
50
53
|
def load_monad(name)
|
51
54
|
path = @paths.fetch(name) {
|
52
|
-
raise ArgumentError, "#{
|
55
|
+
raise ArgumentError, "#{name.inspect} is not a known monad"
|
53
56
|
}
|
54
57
|
Array(path).each { |p| require p }
|
55
58
|
end
|