dry-monads 1.3.5 → 1.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +140 -80
  3. data/LICENSE +1 -1
  4. data/README.md +5 -4
  5. data/dry-monads.gemspec +30 -30
  6. data/lib/dry-monads.rb +1 -1
  7. data/lib/dry/monads.rb +2 -2
  8. data/lib/dry/monads/all.rb +2 -2
  9. data/lib/dry/monads/constants.rb +1 -1
  10. data/lib/dry/monads/do.rb +52 -18
  11. data/lib/dry/monads/do/all.rb +36 -17
  12. data/lib/dry/monads/either.rb +7 -7
  13. data/lib/dry/monads/errors.rb +5 -2
  14. data/lib/dry/monads/lazy.rb +15 -4
  15. data/lib/dry/monads/list.rb +28 -28
  16. data/lib/dry/monads/maybe.rb +87 -19
  17. data/lib/dry/monads/registry.rb +10 -10
  18. data/lib/dry/monads/result.rb +38 -12
  19. data/lib/dry/monads/result/fixed.rb +33 -24
  20. data/lib/dry/monads/right_biased.rb +35 -16
  21. data/lib/dry/monads/task.rb +20 -20
  22. data/lib/dry/monads/transformer.rb +2 -1
  23. data/lib/dry/monads/traverse.rb +7 -1
  24. data/lib/dry/monads/try.rb +45 -12
  25. data/lib/dry/monads/unit.rb +6 -2
  26. data/lib/dry/monads/validated.rb +20 -16
  27. data/lib/dry/monads/version.rb +1 -1
  28. data/lib/json/add/dry/monads/maybe.rb +3 -3
  29. metadata +18 -69
  30. data/.codeclimate.yml +0 -12
  31. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  32. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -30
  33. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  34. data/.github/workflows/ci.yml +0 -52
  35. data/.github/workflows/docsite.yml +0 -34
  36. data/.github/workflows/sync_configs.yml +0 -56
  37. data/.gitignore +0 -10
  38. data/.rspec +0 -4
  39. data/.rubocop.yml +0 -101
  40. data/.yardopts +0 -4
  41. data/CODE_OF_CONDUCT.md +0 -13
  42. data/CONTRIBUTING.md +0 -29
  43. data/Gemfile +0 -19
  44. data/Gemfile.devtools +0 -14
  45. data/Rakefile +0 -8
  46. data/bin/.gitkeep +0 -0
  47. data/bin/console +0 -17
  48. data/bin/setup +0 -7
  49. data/docsite/source/case-equality.html.md +0 -42
  50. data/docsite/source/do-notation.html.md +0 -207
  51. data/docsite/source/getting-started.html.md +0 -142
  52. data/docsite/source/index.html.md +0 -179
  53. data/docsite/source/list.html.md +0 -87
  54. data/docsite/source/maybe.html.md +0 -146
  55. data/docsite/source/pattern-matching.html.md +0 -68
  56. data/docsite/source/result.html.md +0 -190
  57. data/docsite/source/task.html.md +0 -126
  58. data/docsite/source/tracing-failures.html.md +0 -32
  59. data/docsite/source/try.html.md +0 -76
  60. data/docsite/source/unit.html.md +0 -36
  61. data/docsite/source/validated.html.md +0 -88
  62. data/log/.gitkeep +0 -0
  63. data/project.yml +0 -2
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/equalizer'
3
+ require "dry/core/equalizer"
4
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'
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
12
 
13
13
  module Dry
14
14
  module Monads
@@ -83,7 +83,7 @@ module Dry
83
83
  end
84
84
  end
85
85
 
86
- extend Dry::Core::Deprecations[:'dry-monads']
86
+ extend Dry::Core::Deprecations[:"dry-monads"]
87
87
 
88
88
  include Dry::Equalizer(:value, :type)
89
89
  include Transformer
@@ -136,14 +136,14 @@ module Dry
136
136
  end
137
137
 
138
138
  # Maps a block over the list. Acts as `Array#map`.
139
- # Note that this method returns an Array instance, not a List
139
+ # If called without a block, this method returns an enumerator, not a List
140
140
  #
141
141
  # @return [List,Enumerator]
142
142
  def map(&block)
143
- if block
143
+ if block_given?
144
144
  fmap(block)
145
145
  else
146
- value.map(&block)
146
+ value.map
147
147
  end
148
148
  end
149
149
 
@@ -165,7 +165,7 @@ module Dry
165
165
  #
166
166
  # @return [String]
167
167
  def inspect
168
- type_ann = typed? ? "<#{type.name.split('::').last}>" : ''
168
+ type_ann = typed? ? "<#{type.name.split("::").last}>" : ""
169
169
  "List#{type_ann}#{value.inspect}"
170
170
  end
171
171
  alias_method :to_s, :inspect
@@ -192,8 +192,8 @@ module Dry
192
192
  #
193
193
  # @param initial [Object] Initial value
194
194
  # @return [Object]
195
- def fold_left(initial)
196
- value.reduce(initial) { |acc, v| yield(acc, v) }
195
+ def fold_left(initial, &block)
196
+ value.reduce(initial, &block)
197
197
  end
198
198
  alias_method :foldl, :fold_left
199
199
  alias_method :reduce, :fold_left
@@ -224,8 +224,8 @@ module Dry
224
224
  # Filters elements with a block
225
225
  #
226
226
  # @return [List]
227
- def filter
228
- coerce(value.select { |e| yield(e) })
227
+ def filter(&block)
228
+ coerce(value.select(&block))
229
229
  end
230
230
  alias_method :select, :filter
231
231
 
@@ -265,10 +265,10 @@ module Dry
265
265
  def typed(type = nil)
266
266
  if type.nil?
267
267
  if size.zero?
268
- raise ArgumentError, 'Cannot infer a monad for an empty list'
268
+ raise ArgumentError, "Cannot infer a monad for an empty list"
269
269
  else
270
270
  self.class.warn(
271
- 'Automatic monad inference is deprecated, pass a type explicitly '\
271
+ "Automatic monad inference is deprecated, pass a type explicitly "\
272
272
  "or use a predefined constant, e.g. List::Result\n"\
273
273
  "#{caller.find { |l| l !~ %r{(lib/dry/monads)|(gems)} }}"
274
274
  )
@@ -298,7 +298,7 @@ module Dry
298
298
  # @return [Monad] Result is a monadic value
299
299
  def traverse(proc = nil, &block)
300
300
  unless typed?
301
- raise StandardError, 'Cannot traverse an untyped list'
301
+ raise StandardError, "Cannot traverse an untyped list"
302
302
  end
303
303
 
304
304
  cons = type.pure { |list, i| list + List.pure(i) }
@@ -315,8 +315,8 @@ module Dry
315
315
  #
316
316
  # @param list [List]
317
317
  # @return [List]
318
- def apply(list = Undefined)
319
- v = Undefined.default(list) { yield }
318
+ def apply(list = Undefined, &block)
319
+ v = Undefined.default(list, &block)
320
320
  fmap(Curry).bind { |f| v.fmap { |x| f.(x) } }
321
321
  end
322
322
 
@@ -329,7 +329,7 @@ module Dry
329
329
 
330
330
  # Returns self.
331
331
  #
332
- # @return [Result::Success, Result::Failure]
332
+ # @return [List]
333
333
  def to_monad
334
334
  self
335
335
  end
@@ -345,7 +345,7 @@ module Dry
345
345
  # Some(n / divisor)
346
346
  # end
347
347
  # end
348
- # # => List[4, 2]
348
+ # # => List[2, 4]
349
349
  #
350
350
  # @example without block
351
351
  # List[Some(5), None(), Some(3)].collect.map { |x| x * 2 }
@@ -422,10 +422,10 @@ module Dry
422
422
  # List of results
423
423
  Result = ListBuilder[Result]
424
424
 
425
- # List of results
425
+ # List of maybes
426
426
  Maybe = ListBuilder[Maybe]
427
427
 
428
- # List of results
428
+ # List of tries
429
429
  Try = ListBuilder[Try]
430
430
 
431
431
  # List of validation results
@@ -449,9 +449,9 @@ module Dry
449
449
  end
450
450
  end
451
451
 
452
- require 'dry/monads/registry'
452
+ require "dry/monads/registry"
453
453
  register_mixin(:list, List::Mixin)
454
454
  end
455
455
  end
456
456
 
457
- require 'dry/monads/traverse'
457
+ require "dry/monads/traverse"
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/equalizer'
4
- require 'dry/core/deprecations'
3
+ require "dry/core/equalizer"
4
+ require "dry/core/deprecations"
5
+ require "dry/core/class_attributes"
5
6
 
6
- require 'dry/monads/right_biased'
7
- require 'dry/monads/transformer'
8
- require 'dry/monads/unit'
9
- require 'dry/monads/constants'
7
+ require "dry/monads/right_biased"
8
+ require "dry/monads/transformer"
9
+ require "dry/monads/unit"
10
+ require "dry/monads/constants"
10
11
 
11
12
  module Dry
12
13
  module Monads
@@ -15,9 +16,13 @@ module Dry
15
16
  # @api public
16
17
  class Maybe
17
18
  include Transformer
19
+ extend Core::ClassAttributes
20
+
21
+ defines :warn_on_implicit_nil_coercion
22
+ warn_on_implicit_nil_coercion true
18
23
 
19
24
  class << self
20
- extend Core::Deprecations[:'dry-monads']
25
+ extend Core::Deprecations[:"dry-monads"]
21
26
 
22
27
  # Wraps the given value with into a Maybe object.
23
28
  #
@@ -105,7 +110,9 @@ module Dry
105
110
  end
106
111
 
107
112
  def initialize(value = Undefined)
108
- raise ArgumentError, 'nil cannot be some' if value.nil?
113
+ raise ArgumentError, "nil cannot be some" if value.nil?
114
+
115
+ super()
109
116
 
110
117
  @value = Undefined.default(value, Unit)
111
118
  end
@@ -121,14 +128,65 @@ module Dry
121
128
  # @return [Maybe::Some, Maybe::None] Wrapped result, i.e. nil will be mapped to None,
122
129
  # other values will be wrapped with Some
123
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)
124
164
  Maybe.coerce(bind(*args, &block))
125
165
  end
126
- alias_method :maybe, :fmap
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
127
185
 
128
186
  # @return [String]
129
187
  def to_s
130
188
  if Unit.equal?(@value)
131
- 'Some()'
189
+ "Some()"
132
190
  else
133
191
  "Some(#{@value.inspect})"
134
192
  end
@@ -146,7 +204,7 @@ module Dry
146
204
  singleton_class.send(:attr_reader, :instance)
147
205
 
148
206
  # @api private
149
- def self.method_missing(m, *)
207
+ def self.method_missing(m, *) # rubocop:disable Style/MissingRespondToMissing
150
208
  if (instance.methods(true) - methods(true)).include?(m)
151
209
  raise ConstructorNotAppliedError.new(m, :None)
152
210
  else
@@ -161,6 +219,7 @@ module Dry
161
219
  attr_reader :trace
162
220
 
163
221
  def initialize(trace = RightBiased::Left.trace_caller)
222
+ super()
164
223
  @trace = trace
165
224
  end
166
225
 
@@ -202,7 +261,7 @@ module Dry
202
261
 
203
262
  # @return [String]
204
263
  def to_s
205
- 'None'
264
+ "None"
206
265
  end
207
266
  alias_method :inspect, :to_s
208
267
 
@@ -230,6 +289,13 @@ module Dry
230
289
  def deconstruct
231
290
  EMPTY_ARRAY
232
291
  end
292
+
293
+ # @see Maybe::Some#filter
294
+ #
295
+ # @return [Maybe::None]
296
+ def filter(_ = Undefined)
297
+ self
298
+ end
233
299
  end
234
300
 
235
301
  # A module that can be included for easier access to Maybe monads.
@@ -301,9 +367,9 @@ module Dry
301
367
  # None values are removed
302
368
  #
303
369
  # @example
304
- # Maybe::Hash.filter(foo: Some(1), bar: Some(2)) # => Some(foo: 1, bar: 2)
305
- # Maybe::Hash.filter(foo: Some(1), bar: None()) # => None()
306
- # Maybe::Hash.filter(foo: None(), bar: Some(2)) # => None()
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 }
307
373
  #
308
374
  # @param hash [::Hash<Object,Maybe>]
309
375
  # @return [::Hash]
@@ -325,10 +391,12 @@ module Dry
325
391
 
326
392
  class Result
327
393
  class Success < Result
328
- # @return [Maybe::Some]
394
+ extend Core::Deprecations[:"dry-monads"]
395
+
396
+ # @return [Maybe]
329
397
  def to_maybe
330
- Kernel.warn 'Success(nil) transformed to None' if @value.nil?
331
- Dry::Monads::Maybe(@value)
398
+ warn "Success(nil) transformed to None" if @value.nil?
399
+ ::Dry::Monads::Maybe(@value)
332
400
  end
333
401
  end
334
402
 
@@ -389,7 +457,7 @@ module Dry
389
457
  end
390
458
  end
391
459
 
392
- require 'dry/monads/registry'
460
+ require "dry/monads/registry"
393
461
  register_mixin(:maybe, Maybe::Mixin)
394
462
  end
395
463
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/map'
3
+ require "concurrent/map"
4
4
 
5
5
  module Dry
6
6
  # Common, idiomatic monads for Ruby
@@ -10,16 +10,16 @@ module Dry
10
10
  @registry = {}
11
11
  @constructors = nil
12
12
  @paths = {
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',
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",
20
20
  result: [
21
- 'dry/monads/result',
22
- 'dry/monads/result/fixed'
21
+ "dry/monads/result",
22
+ "dry/monads/result/fixed"
23
23
  ]
24
24
  }.freeze
25
25
  @mixins = Concurrent::Map.new
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/equalizer'
3
+ require "dry/core/equalizer"
4
4
 
5
- require 'dry/monads/constants'
6
- require 'dry/monads/right_biased'
7
- require 'dry/monads/transformer'
8
- require 'dry/monads/conversion_stubs'
9
- require 'dry/monads/unit'
5
+ require "dry/monads/constants"
6
+ require "dry/monads/right_biased"
7
+ require "dry/monads/transformer"
8
+ require "dry/monads/conversion_stubs"
9
+ require "dry/monads/unit"
10
10
 
11
11
  module Dry
12
12
  module Monads
@@ -86,6 +86,7 @@ module Dry
86
86
 
87
87
  # @param value [Object] a value of a successful operation
88
88
  def initialize(value)
89
+ super()
89
90
  @value = value
90
91
  end
91
92
 
@@ -134,7 +135,7 @@ module Dry
134
135
  # @return [String]
135
136
  def to_s
136
137
  if Unit.equal?(@value)
137
- 'Success()'
138
+ "Success()"
138
139
  else
139
140
  "Success(#{@value.inspect})"
140
141
  end
@@ -147,6 +148,13 @@ module Dry
147
148
  def flip
148
149
  Failure.new(@value, RightBiased::Left.trace_caller)
149
150
  end
151
+
152
+ # Ignores values and returns self, see {Failure#alt_map}
153
+ #
154
+ # @return [Result::Success]
155
+ def alt_map(_ = nil)
156
+ self
157
+ end
150
158
  end
151
159
 
152
160
  # Represents a value of a failed operation.
@@ -188,6 +196,7 @@ module Dry
188
196
  # @param value [Object] failure value
189
197
  # @param trace [String] caller line
190
198
  def initialize(value, trace = RightBiased::Left.trace_caller)
199
+ super()
191
200
  @value = value
192
201
  @trace = trace
193
202
  end
@@ -218,7 +227,8 @@ module Dry
218
227
  # otherwise simply returns the first argument.
219
228
  #
220
229
  # @example
221
- # Dry::Monads.Failure(ArgumentError.new('error message')).or(&:message) # => "error message"
230
+ # Dry::Monads.Failure(ArgumentError.new('error message')).or(&:message)
231
+ # # => "error message"
222
232
  #
223
233
  # @param args [Array<Object>] arguments that will be passed to a block
224
234
  # if one was given, otherwise the first
@@ -232,7 +242,8 @@ module Dry
232
242
  end
233
243
  end
234
244
 
235
- # A lifted version of `#or`. Wraps the passed value or the block result with Result::Success.
245
+ # A lifted version of `#or`. Wraps the passed value or the block
246
+ # result with Result::Success.
236
247
  #
237
248
  # @example
238
249
  # Dry::Monads.Failure.new('no value').or_fmap('value') # => Success("value")
@@ -247,7 +258,7 @@ module Dry
247
258
  # @return [String]
248
259
  def to_s
249
260
  if Unit.equal?(@value)
250
- 'Failure()'
261
+ "Failure()"
251
262
  else
252
263
  "Failure(#{@value.inspect})"
253
264
  end
@@ -287,6 +298,21 @@ module Dry
287
298
  def either(_, g)
288
299
  g.(failure)
289
300
  end
301
+
302
+ # Lifts a block/proc over Failure
303
+ #
304
+ # @overload alt_map(proc)
305
+ # @param proc [#call]
306
+ # @return [Result::Failure]
307
+ #
308
+ # @overload alt_map
309
+ # @param block [Proc]
310
+ # @return [Result::Failure]
311
+ #
312
+ def alt_map(proc = Undefined, &block)
313
+ f = Undefined.default(proc, block)
314
+ self.class.new(f.(failure), RightBiased::Left.trace_caller)
315
+ end
290
316
  end
291
317
 
292
318
  # A module that can be included for easier access to Result monads.
@@ -448,7 +474,7 @@ module Dry
448
474
  end
449
475
 
450
476
  class Invalid < Validated
451
- # Concerts to Result::Failure
477
+ # Converts to Result::Failure
452
478
  #
453
479
  # @return [Result::Failure]
454
480
  def to_result
@@ -457,7 +483,7 @@ module Dry
457
483
  end
458
484
  end
459
485
 
460
- require 'dry/monads/registry'
486
+ require "dry/monads/registry"
461
487
  register_mixin(:result, Result::Mixin)
462
488
  end
463
489
  end