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,37 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dry::Monads
4
- class Result
5
- # @see Monads#Result
6
- # @private
7
- class Fixed < Module
8
- def self.[](error, **options)
9
- new(error, **options)
10
- end
3
+ require "dry/monads/constants"
4
+
5
+ module Dry
6
+ module Monads
7
+ class Result
8
+ # @see Monads#Result
9
+ # @private
10
+ class Fixed < ::Module
11
+ def self.[](error, **options)
12
+ new(error, **options)
13
+ end
14
+
15
+ def initialize(error, **_options)
16
+ super()
11
17
 
12
- def initialize(error, **_options)
13
- @mod = Module.new do
14
- define_method(:Failure) do |value|
15
- if error === value
16
- Failure.new(value, RightBiased::Left.trace_caller)
17
- else
18
- raise InvalidFailureTypeError, value
18
+ @mod = ::Module.new do
19
+ define_method(:Failure) do |value|
20
+ if error === value
21
+ Failure.new(value, RightBiased::Left.trace_caller)
22
+ else
23
+ # rubocop:disable Style/RaiseArgs
24
+ # per https://github.com/dry-rb/dry-monads/pull/142
25
+ raise InvalidFailureTypeError.new(value)
26
+ # rubocop:enable Style/RaiseArgs
27
+ end
19
28
  end
20
- end
21
29
 
22
- def Success(value = Undefined, &block)
23
- v = Undefined.default(value, block || Unit)
24
- Success.new(v)
30
+ def Success(value = Undefined, &block)
31
+ v = Undefined.default(value, block || Unit)
32
+ Success.new(v)
33
+ end
25
34
  end
26
35
  end
27
- end
28
36
 
29
- private
37
+ private
30
38
 
31
- def included(base)
32
- super
39
+ def included(base)
40
+ super
33
41
 
34
- base.include(@mod)
42
+ base.include(@mod)
43
+ end
35
44
  end
36
45
  end
37
46
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/monads/constants'
4
- require 'dry/monads/unit'
5
- require 'dry/monads/curry'
6
- require 'dry/monads/errors'
3
+ require "dry/monads/constants"
4
+ require "dry/monads/unit"
5
+ require "dry/monads/curry"
6
+ require "dry/monads/errors"
7
7
 
8
8
  module Dry
9
9
  module Monads
@@ -12,7 +12,7 @@ module Dry
12
12
  # Right part
13
13
  #
14
14
  # @api public
15
- module Right
15
+ module Right # rubocop:disable Metrics/ModuleLength
16
16
  # @private
17
17
  def self.included(m)
18
18
  super
@@ -90,6 +90,16 @@ module Dry
90
90
  self
91
91
  end
92
92
 
93
+ # Ignores arguments and returns self. It exists to keep the interface
94
+ # identical to that of {RightBiased::Left}.
95
+ #
96
+ # @param _alt [RightBiased::Right, RightBiased::Left]
97
+ #
98
+ # @return [RightBiased::Right]
99
+ def |(_alt)
100
+ self
101
+ end
102
+
93
103
  # A lifted version of `#or`. For {RightBiased::Right} acts in the same way as `#or`,
94
104
  # that is returns itselt.
95
105
  #
@@ -118,18 +128,18 @@ module Dry
118
128
  # create_user.apply(name) # => Failure(:name_missing)
119
129
  #
120
130
  # @return [RightBiased::Left,RightBiased::Right]
121
- def apply(val = Undefined)
131
+ def apply(val = Undefined, &block)
122
132
  unless @value.respond_to?(:call)
123
133
  raise TypeError, "Cannot apply #{val.inspect} to #{@value.inspect}"
124
134
  end
125
135
 
126
- Undefined.default(val) { yield }.fmap { |unwrapped| curry.(unwrapped) }
136
+ Undefined.default(val, &block).fmap { |unwrapped| curry.(unwrapped) }
127
137
  end
128
138
 
129
139
  # @param other [Object]
130
140
  # @return [Boolean]
131
141
  def ===(other)
132
- self.class == other.class && value! === other.value!
142
+ other.instance_of?(self.class) && value! === other.value!
133
143
  end
134
144
 
135
145
  # Maps the value to Dry::Monads::Unit, useful when you don't care
@@ -197,11 +207,11 @@ module Dry
197
207
  # @api private
198
208
  def deconstruct
199
209
  if Unit.equal?(@value)
200
- []
201
- elsif @value.is_a?(::Array)
202
- @value
203
- else
210
+ EMPTY_ARRAY
211
+ elsif !@value.is_a?(::Array)
204
212
  [@value]
213
+ else
214
+ @value
205
215
  end
206
216
  end
207
217
 
@@ -226,7 +236,7 @@ module Dry
226
236
 
227
237
  private
228
238
 
229
- if RUBY_VERSION >= '2.7'
239
+ if RUBY_VERSION >= "2.7"
230
240
  # @api private
231
241
  def destructure(value)
232
242
  if value.is_a?(::Hash)
@@ -255,7 +265,7 @@ module Dry
255
265
  # @private
256
266
  # @return [String] Caller location
257
267
  def self.trace_caller
258
- caller_locations(2, 2)[0].to_s
268
+ caller_locations(2, 1)[0].to_s
259
269
  end
260
270
 
261
271
  # Raises an error on accessing internal value
@@ -299,11 +309,20 @@ module Dry
299
309
  raise NotImplementedError
300
310
  end
301
311
 
312
+ # Returns the passed value. Works in pair with {RightBiased::Right#|}.
313
+ #
314
+ # @param alt [RightBiased::Right, RightBiased::Left]
315
+ #
316
+ # @return [RightBiased::Right, RightBiased::Left]
317
+ def |(alt)
318
+ self.or(alt)
319
+ end
320
+
302
321
  # A lifted version of `#or`. This is basically `#or` + `#fmap`.
303
322
  #
304
323
  # @example
305
- # Dry::Monads.None.or('no value') # => Some("no value")
306
- # Dry::Monads.None.or { Time.now } # => Some(current time)
324
+ # Dry::Monads.None.or_fmap('no value') # => Some("no value")
325
+ # Dry::Monads.None.or_fmap { Time.now } # => Some(current time)
307
326
  #
308
327
  # @return [RightBiased::Left, RightBiased::Right]
309
328
  def or_fmap(*)
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/promise'
3
+ require "concurrent/promise"
4
4
 
5
- require 'dry/monads/unit'
6
- require 'dry/monads/curry'
7
- require 'dry/monads/conversion_stubs'
5
+ require "dry/monads/unit"
6
+ require "dry/monads/curry"
7
+ require "dry/monads/conversion_stubs"
8
8
 
9
9
  module Dry
10
10
  module Monads
@@ -48,9 +48,11 @@ module Dry
48
48
  # @example using a predefined executor
49
49
  # Task[:fast] { do_quick_task }
50
50
  #
51
- # @param executor [Concurrent::AbstractExecutorService,Symbol] Either an executor instance
52
- # or a name of predefined global
53
- # from concurrent-ruby
51
+ # @param executor [Concurrent::AbstractExecutorService,Symbol]
52
+ # Either an executor instance
53
+ # or a name of predefined global
54
+ # from concurrent-ruby
55
+ #
54
56
  # @return [Task]
55
57
  def [](executor, &block)
56
58
  new(Promise.execute(executor: executor, &block))
@@ -131,14 +133,14 @@ module Dry
131
133
  state = case promise.state
132
134
  when :fulfilled
133
135
  if Unit.equal?(value!)
134
- 'value=()'
136
+ "value=()"
135
137
  else
136
138
  "value=#{value!.inspect}"
137
139
  end
138
140
  when :rejected
139
141
  "error=#{promise.reason.inspect}"
140
142
  else
141
- '?'
143
+ "?"
142
144
  end
143
145
 
144
146
  "Task(#{state})"
@@ -164,14 +166,12 @@ module Dry
164
166
  )
165
167
 
166
168
  promise.on_error do |v|
167
- begin
168
- inner = block.(v).promise
169
- inner.execute
170
- inner.on_success { |r| child.on_fulfill(r) }
171
- inner.on_error { |e| child.on_reject(e) }
172
- rescue StandardError => e
173
- child.on_reject(e)
174
- end
169
+ inner = block.(v).promise
170
+ inner.execute
171
+ inner.on_success { |r| child.on_fulfill(r) }
172
+ inner.on_error { |e| child.on_reject(e) }
173
+ rescue StandardError => e
174
+ child.on_reject(e)
175
175
  end
176
176
  promise.on_success { |v| child.on_fulfill(v) }
177
177
 
@@ -236,8 +236,8 @@ module Dry
236
236
  #
237
237
  # @param val [Task]
238
238
  # @return [Task]
239
- def apply(val = Undefined)
240
- arg = Undefined.default(val) { yield }
239
+ def apply(val = Undefined, &block)
240
+ arg = Undefined.default(val, &block)
241
241
  bind { |f| arg.fmap { |v| curry(f).(v) } }
242
242
  end
243
243
 
@@ -314,7 +314,7 @@ module Dry
314
314
  end
315
315
  end
316
316
 
317
- require 'dry/monads/registry'
317
+ require "dry/monads/registry"
318
318
  register_mixin(:task, Task::Mixin)
319
319
  end
320
320
  end
@@ -27,7 +27,8 @@ module Dry
27
27
  # Lifts a block/proc over the 3-level nested structure.
28
28
  #
29
29
  # @example
30
- # List[Right(Some(1)), Left(Some(1))].fmap3 { |x| x + 1 } # => List[Right(Some(2)), Left(Some(1))]
30
+ # List[Right(Some(1)), Left(Some(1))].fmap3 { |x| x + 1 }
31
+ # # => List[Right(Some(2)), Left(Some(1))]
31
32
  # Right(None).fmap3 { |x| x + 1 } # => Right(None)
32
33
  #
33
34
  # @param args [Array<Object>] arguments will be passed to the block or the proc
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/monads/validated'
3
+ # rubocop:disable Naming/ConstantName
4
+ # rubocop:disable Style/MutableConstant
5
+
6
+ require "dry/monads/validated"
4
7
 
5
8
  module Dry
6
9
  module Monads
@@ -20,3 +23,6 @@ module Dry
20
23
  Traverse.freeze
21
24
  end
22
25
  end
26
+
27
+ # rubocop:enable Style/MutableConstant
28
+ # rubocop:enable Naming/ConstantName
@@ -1,10 +1,10 @@
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
5
 
6
- require 'dry/monads/right_biased'
7
- require 'dry/monads/conversion_stubs'
6
+ require "dry/monads/right_biased"
7
+ require "dry/monads/conversion_stubs"
8
8
 
9
9
  module Dry
10
10
  module Monads
@@ -22,7 +22,7 @@ module Dry
22
22
  attr_reader :exception
23
23
 
24
24
  class << self
25
- extend Core::Deprecations[:'dry-monads']
25
+ extend Core::Deprecations[:"dry-monads"]
26
26
 
27
27
  # Invokes a callable and if successful stores the result in the
28
28
  # {Try::Value} type, but if one of the specified exceptions was raised it stores
@@ -72,7 +72,7 @@ module Dry
72
72
  # @param exceptions [Array<Exception>]
73
73
  # @return [Try::Value,Try::Error]
74
74
  def [](*exceptions, &block)
75
- raise ArgumentError, 'At least one exception type required' if exceptions.empty?
75
+ raise ArgumentError, "At least one exception type required" if exceptions.empty?
76
76
 
77
77
  run(exceptions, block)
78
78
  end
@@ -92,7 +92,7 @@ module Dry
92
92
 
93
93
  # Returns self.
94
94
  #
95
- # @return [Maybe::Some, Maybe::None]
95
+ # @return [Try::Value, Try::Error]
96
96
  def to_monad
97
97
  self
98
98
  end
@@ -110,6 +110,8 @@ module Dry
110
110
  # @param exceptions [Array<Exception>] list of exceptions to be rescued
111
111
  # @param value [Object] the value to be stored in the monad
112
112
  def initialize(exceptions, value)
113
+ super()
114
+
113
115
  @catchable = exceptions
114
116
  @value = value
115
117
  end
@@ -160,12 +162,21 @@ module Dry
160
162
  # @return [String]
161
163
  def to_s
162
164
  if Unit.equal?(@value)
163
- 'Try::Value()'
165
+ "Try::Value()"
164
166
  else
165
167
  "Try::Value(#{@value.inspect})"
166
168
  end
167
169
  end
168
170
  alias_method :inspect, :to_s
171
+
172
+ # Ignores values and returns self, see {Try::Error#recover}
173
+ #
174
+ # @param errors [Class] List of Exception subclasses
175
+ #
176
+ # @return [Try::Value]
177
+ def recover(*_errors)
178
+ self
179
+ end
169
180
  end
170
181
 
171
182
  # Represents a result of a failed execution.
@@ -179,6 +190,8 @@ module Dry
179
190
 
180
191
  # @param exception [Exception]
181
192
  def initialize(exception)
193
+ super()
194
+
182
195
  @exception = exception
183
196
  end
184
197
 
@@ -211,6 +224,26 @@ module Dry
211
224
  def ===(other)
212
225
  Error === other && exception === other.exception
213
226
  end
227
+
228
+ # Acts in a similar way to `rescue`. It checks if
229
+ # {exception} is one of {errors} and yields the block if so.
230
+ #
231
+ # @param errors [Class] List of Exception subclasses
232
+ #
233
+ # @return [Try::Value]
234
+ def recover(*errors)
235
+ if errors.empty?
236
+ classes = DEFAULT_EXCEPTIONS
237
+ else
238
+ classes = errors
239
+ end
240
+
241
+ if classes.any? { |c| c === exception }
242
+ Value.new([exception.class], yield(exception))
243
+ else
244
+ self
245
+ end
246
+ end
214
247
  end
215
248
 
216
249
  # A module that can be included for easier access to Try monads.
@@ -261,9 +294,9 @@ module Dry
261
294
  #
262
295
  def Value(value = Undefined, exceptions = DEFAULT_EXCEPTIONS, &block)
263
296
  v = Undefined.default(value, block)
264
- raise ArgumentError, 'No value given' if !value.nil? && v.nil?
297
+ raise ArgumentError, "No value given" if !value.nil? && v.nil?
265
298
 
266
- Value.new(exceptions, v)
299
+ Try::Value.new(exceptions, v)
267
300
  end
268
301
 
269
302
  # Error constructor
@@ -278,14 +311,14 @@ module Dry
278
311
  #
279
312
  def Error(error = Undefined, &block)
280
313
  v = Undefined.default(error, block)
281
- raise ArgumentError, 'No value given' if v.nil?
314
+ raise ArgumentError, "No value given" if v.nil?
282
315
 
283
316
  Try::Error.new(v)
284
317
  end
285
318
  end
286
319
  end
287
320
 
288
- require 'dry/monads/registry'
321
+ require "dry/monads/registry"
289
322
  register_mixin(:try, Try::Mixin)
290
323
  end
291
324
  end
@@ -20,11 +20,15 @@ module Dry
20
20
  #
21
21
  Unit = Object.new.tap do |unit|
22
22
  def unit.to_s
23
- 'Unit'
23
+ "Unit"
24
24
  end
25
25
 
26
26
  def unit.inspect
27
- 'Unit'
27
+ "Unit"
28
+ end
29
+
30
+ def unit.deconstruct
31
+ EMPTY_ARRAY
28
32
  end
29
33
  end
30
34
  end