dry-monads 1.3.5 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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