dry-monads 1.3.5 → 1.5.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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +152 -80
  3. data/LICENSE +1 -1
  4. data/README.md +5 -4
  5. data/dry-monads.gemspec +31 -30
  6. data/lib/dry/monads/all.rb +2 -3
  7. data/lib/dry/monads/constants.rb +0 -2
  8. data/lib/dry/monads/curry.rb +2 -2
  9. data/lib/dry/monads/do/all.rb +35 -18
  10. data/lib/dry/monads/do.rb +48 -20
  11. data/lib/dry/monads/errors.rb +8 -5
  12. data/lib/dry/monads/lazy.rb +13 -5
  13. data/lib/dry/monads/list.rb +27 -37
  14. data/lib/dry/monads/maybe.rb +85 -26
  15. data/lib/dry/monads/registry.rb +20 -20
  16. data/lib/dry/monads/result/fixed.rb +31 -24
  17. data/lib/dry/monads/result.rb +37 -19
  18. data/lib/dry/monads/right_biased.rb +38 -31
  19. data/lib/dry/monads/task.rb +25 -28
  20. data/lib/dry/monads/transformer.rb +2 -1
  21. data/lib/dry/monads/traverse.rb +5 -1
  22. data/lib/dry/monads/try.rb +45 -18
  23. data/lib/dry/monads/unit.rb +9 -3
  24. data/lib/dry/monads/validated.rb +18 -18
  25. data/lib/dry/monads/version.rb +1 -1
  26. data/lib/dry/monads.rb +25 -4
  27. data/lib/dry-monads.rb +1 -1
  28. data/lib/json/add/dry/monads/maybe.rb +5 -5
  29. metadata +22 -54
  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/lib/dry/monads/either.rb +0 -66
  63. data/log/.gitkeep +0 -0
  64. data/project.yml +0 -2
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/map'
4
-
5
3
  module Dry
6
4
  # Common, idiomatic monads for Ruby
7
5
  #
@@ -9,20 +7,20 @@ module Dry
9
7
  module Monads
10
8
  @registry = {}
11
9
  @constructors = nil
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',
10
+ @constants = {
11
+ do: "Do::All",
12
+ lazy: "Lazy",
13
+ list: "List",
14
+ maybe: "Maybe",
15
+ task: "Task",
16
+ try: "Try",
17
+ validated: "Validated",
20
18
  result: [
21
- 'dry/monads/result',
22
- 'dry/monads/result/fixed'
19
+ "Result",
20
+ "Result::Fixed"
23
21
  ]
24
22
  }.freeze
25
- @mixins = Concurrent::Map.new
23
+ @mixins = ::Concurrent::Map.new
26
24
 
27
25
  class << self
28
26
  private
@@ -46,27 +44,29 @@ module Dry
46
44
 
47
45
  # @private
48
46
  def known_monads
49
- @paths.keys
47
+ @constants.keys
50
48
  end
51
49
 
52
50
  # @private
53
51
  def load_monad(name)
54
- path = @paths.fetch(name) {
55
- raise ArgumentError, "#{name.inspect} is not a known monad"
52
+ constants = @constants.fetch(name) {
53
+ raise ::ArgumentError, "#{name.inspect} is not a known monad"
56
54
  }
57
- Array(path).each { |p| require p }
55
+ Array(constants).each do |const_name|
56
+ const_name.split("::").reduce(Monads) { |mod, const| mod.const_get(const) }
57
+ end
58
58
  end
59
59
 
60
60
  # @private
61
61
  def constructors
62
- @constructors ||= registry.values.map { |m|
62
+ @constructors ||= registry.values.filter_map { |m|
63
63
  m::Constructors if m.const_defined?(:Constructors)
64
- }.compact
64
+ }
65
65
  end
66
66
 
67
67
  # @private
68
68
  def all_loaded?
69
- registry.size == @paths.size
69
+ registry.size.eql?(@constants.size)
70
70
  end
71
71
  end
72
72
  end
@@ -1,37 +1,44 @@
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
+ # per https://github.com/dry-rb/dry-monads/pull/142
24
+ raise InvalidFailureTypeError.new(value)
25
+ end
19
26
  end
20
- end
21
27
 
22
- def Success(value = Undefined, &block)
23
- v = Undefined.default(value, block || Unit)
24
- Success.new(v)
28
+ def Success(value = Undefined, &block)
29
+ v = Undefined.default(value, block || Unit)
30
+ Success.new(v)
31
+ end
25
32
  end
26
33
  end
27
- end
28
34
 
29
- private
35
+ private
30
36
 
31
- def included(base)
32
- super
37
+ def included(base)
38
+ super
33
39
 
34
- base.include(@mod)
40
+ base.include(@mod)
41
+ end
35
42
  end
36
43
  end
37
44
  end
@@ -1,13 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/equalizer'
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'
10
-
11
3
  module Dry
12
4
  module Monads
13
5
  # Represents an operation which either succeeded or failed.
@@ -86,6 +78,7 @@ module Dry
86
78
 
87
79
  # @param value [Object] a value of a successful operation
88
80
  def initialize(value)
81
+ super()
89
82
  @value = value
90
83
  end
91
84
 
@@ -115,8 +108,8 @@ module Dry
115
108
  #
116
109
  # @param args [Array<Object>] arguments will be transparently passed through to #bind
117
110
  # @return [Result::Success]
118
- def fmap(*args, &block)
119
- Success.new(bind(*args, &block))
111
+ def fmap(...)
112
+ Success.new(bind(...))
120
113
  end
121
114
 
122
115
  # Returns result of applying first function to the internal value.
@@ -134,7 +127,7 @@ module Dry
134
127
  # @return [String]
135
128
  def to_s
136
129
  if Unit.equal?(@value)
137
- 'Success()'
130
+ "Success()"
138
131
  else
139
132
  "Success(#{@value.inspect})"
140
133
  end
@@ -147,6 +140,13 @@ module Dry
147
140
  def flip
148
141
  Failure.new(@value, RightBiased::Left.trace_caller)
149
142
  end
143
+
144
+ # Ignores values and returns self, see {Failure#alt_map}
145
+ #
146
+ # @return [Result::Success]
147
+ def alt_map(_ = nil)
148
+ self
149
+ end
150
150
  end
151
151
 
152
152
  # Represents a value of a failed operation.
@@ -156,7 +156,7 @@ module Dry
156
156
  include RightBiased::Left
157
157
  include Dry::Equalizer(:failure)
158
158
 
159
- singleton_class.send(:alias_method, :call, :new)
159
+ singleton_class.alias_method(:call, :new)
160
160
 
161
161
  # Shortcut for Failure([...])
162
162
  #
@@ -188,6 +188,7 @@ module Dry
188
188
  # @param value [Object] failure value
189
189
  # @param trace [String] caller line
190
190
  def initialize(value, trace = RightBiased::Left.trace_caller)
191
+ super()
191
192
  @value = value
192
193
  @trace = trace
193
194
  end
@@ -218,7 +219,8 @@ module Dry
218
219
  # otherwise simply returns the first argument.
219
220
  #
220
221
  # @example
221
- # Dry::Monads.Failure(ArgumentError.new('error message')).or(&:message) # => "error message"
222
+ # Dry::Monads.Failure(ArgumentError.new('error message')).or(&:message)
223
+ # # => "error message"
222
224
  #
223
225
  # @param args [Array<Object>] arguments that will be passed to a block
224
226
  # if one was given, otherwise the first
@@ -232,7 +234,8 @@ module Dry
232
234
  end
233
235
  end
234
236
 
235
- # A lifted version of `#or`. Wraps the passed value or the block result with Result::Success.
237
+ # A lifted version of `#or`. Wraps the passed value or the block
238
+ # result with Result::Success.
236
239
  #
237
240
  # @example
238
241
  # Dry::Monads.Failure.new('no value').or_fmap('value') # => Success("value")
@@ -240,14 +243,14 @@ module Dry
240
243
  #
241
244
  # @param args [Array<Object>] arguments will be passed to the underlying `#or` call
242
245
  # @return [Result::Success] Wrapped value
243
- def or_fmap(*args, &block)
244
- Success.new(self.or(*args, &block))
246
+ def or_fmap(...)
247
+ Success.new(self.or(...))
245
248
  end
246
249
 
247
250
  # @return [String]
248
251
  def to_s
249
252
  if Unit.equal?(@value)
250
- 'Failure()'
253
+ "Failure()"
251
254
  else
252
255
  "Failure(#{@value.inspect})"
253
256
  end
@@ -287,6 +290,21 @@ module Dry
287
290
  def either(_, g)
288
291
  g.(failure)
289
292
  end
293
+
294
+ # Lifts a block/proc over Failure
295
+ #
296
+ # @overload alt_map(proc)
297
+ # @param proc [#call]
298
+ # @return [Result::Failure]
299
+ #
300
+ # @overload alt_map
301
+ # @param block [Proc]
302
+ # @return [Result::Failure]
303
+ #
304
+ def alt_map(proc = Undefined, &block)
305
+ f = Undefined.default(proc, block)
306
+ self.class.new(f.(failure), RightBiased::Left.trace_caller)
307
+ end
290
308
  end
291
309
 
292
310
  # A module that can be included for easier access to Result monads.
@@ -448,7 +466,7 @@ module Dry
448
466
  end
449
467
 
450
468
  class Invalid < Validated
451
- # Concerts to Result::Failure
469
+ # Converts to Result::Failure
452
470
  #
453
471
  # @return [Result::Failure]
454
472
  def to_result
@@ -457,7 +475,7 @@ module Dry
457
475
  end
458
476
  end
459
477
 
460
- require 'dry/monads/registry'
478
+ require "dry/monads/registry"
461
479
  register_mixin(:result, Result::Mixin)
462
480
  end
463
481
  end
@@ -1,10 +1,5 @@
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'
7
-
8
3
  module Dry
9
4
  module Monads
10
5
  # A common module for right-biased monads, such as Result/Either, Maybe, and Try.
@@ -20,7 +15,7 @@ module Dry
20
15
  def m.to_proc
21
16
  @to_proc ||= method(:new).to_proc
22
17
  end
23
- m.singleton_class.send(:alias_method, :call, :new)
18
+ m.singleton_class.alias_method(:call, :new)
24
19
  end
25
20
 
26
21
  # Unwraps the underlying value
@@ -70,8 +65,8 @@ module Dry
70
65
  #
71
66
  # @param [Array<Object>] args arguments will be transparently passed through to #bind
72
67
  # @return [RightBiased::Right]
73
- def tee(*args, &block)
74
- bind(*args, &block).bind { self }
68
+ def tee(...)
69
+ bind(...).bind { self }
75
70
  end
76
71
 
77
72
  # Abstract method for lifting a block over the monad type.
@@ -90,6 +85,16 @@ module Dry
90
85
  self
91
86
  end
92
87
 
88
+ # Ignores arguments and returns self. It exists to keep the interface
89
+ # identical to that of {RightBiased::Left}.
90
+ #
91
+ # @param _alt [RightBiased::Right, RightBiased::Left]
92
+ #
93
+ # @return [RightBiased::Right]
94
+ def |(_alt)
95
+ self
96
+ end
97
+
93
98
  # A lifted version of `#or`. For {RightBiased::Right} acts in the same way as `#or`,
94
99
  # that is returns itselt.
95
100
  #
@@ -118,18 +123,18 @@ module Dry
118
123
  # create_user.apply(name) # => Failure(:name_missing)
119
124
  #
120
125
  # @return [RightBiased::Left,RightBiased::Right]
121
- def apply(val = Undefined)
126
+ def apply(val = Undefined, &block)
122
127
  unless @value.respond_to?(:call)
123
128
  raise TypeError, "Cannot apply #{val.inspect} to #{@value.inspect}"
124
129
  end
125
130
 
126
- Undefined.default(val) { yield }.fmap { |unwrapped| curry.(unwrapped) }
131
+ Undefined.default(val, &block).fmap { curry.(_1) }
127
132
  end
128
133
 
129
134
  # @param other [Object]
130
135
  # @return [Boolean]
131
136
  def ===(other)
132
- self.class == other.class && value! === other.value!
137
+ other.instance_of?(self.class) && value! === other.value!
133
138
  end
134
139
 
135
140
  # Maps the value to Dry::Monads::Unit, useful when you don't care
@@ -197,11 +202,11 @@ module Dry
197
202
  # @api private
198
203
  def deconstruct
199
204
  if Unit.equal?(@value)
200
- []
201
- elsif @value.is_a?(::Array)
202
- @value
203
- else
205
+ EMPTY_ARRAY
206
+ elsif !@value.is_a?(::Array)
204
207
  [@value]
208
+ else
209
+ @value
205
210
  end
206
211
  end
207
212
 
@@ -226,19 +231,12 @@ module Dry
226
231
 
227
232
  private
228
233
 
229
- if RUBY_VERSION >= '2.7'
230
- # @api private
231
- def destructure(value)
232
- if value.is_a?(::Hash)
233
- [EMPTY_ARRAY, value]
234
- else
235
- [[value], EMPTY_HASH]
236
- end
237
- end
238
- else
239
- # @api private
240
- def destructure(*args, **kwargs)
241
- [args, kwargs]
234
+ # @api private
235
+ def destructure(value)
236
+ if value.is_a?(::Hash)
237
+ [EMPTY_ARRAY, value]
238
+ else
239
+ [[value], EMPTY_HASH]
242
240
  end
243
241
  end
244
242
 
@@ -255,7 +253,7 @@ module Dry
255
253
  # @private
256
254
  # @return [String] Caller location
257
255
  def self.trace_caller
258
- caller_locations(2, 2)[0].to_s
256
+ caller_locations(2, 1)[0].to_s
259
257
  end
260
258
 
261
259
  # Raises an error on accessing internal value
@@ -299,11 +297,20 @@ module Dry
299
297
  raise NotImplementedError
300
298
  end
301
299
 
300
+ # Returns the passed value. Works in pair with {RightBiased::Right#|}.
301
+ #
302
+ # @param alt [RightBiased::Right, RightBiased::Left]
303
+ #
304
+ # @return [RightBiased::Right, RightBiased::Left]
305
+ def |(alt)
306
+ self.or(alt)
307
+ end
308
+
302
309
  # A lifted version of `#or`. This is basically `#or` + `#fmap`.
303
310
  #
304
311
  # @example
305
- # Dry::Monads.None.or('no value') # => Some("no value")
306
- # Dry::Monads.None.or { Time.now } # => Some(current time)
312
+ # Dry::Monads.None.or_fmap('no value') # => Some("no value")
313
+ # Dry::Monads.None.or_fmap { Time.now } # => Some(current time)
307
314
  #
308
315
  # @return [RightBiased::Left, RightBiased::Right]
309
316
  def or_fmap(*)
@@ -1,10 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/promise'
4
-
5
- require 'dry/monads/unit'
6
- require 'dry/monads/curry'
7
- require 'dry/monads/conversion_stubs'
3
+ require "concurrent/promise"
8
4
 
9
5
  module Dry
10
6
  module Monads
@@ -15,7 +11,7 @@ module Dry
15
11
  # @api public
16
12
  class Task
17
13
  # @api private
18
- class Promise < Concurrent::Promise
14
+ class Promise < ::Concurrent::Promise
19
15
  public :on_fulfill, :on_reject
20
16
  end
21
17
  private_constant :Promise
@@ -48,9 +44,11 @@ module Dry
48
44
  # @example using a predefined executor
49
45
  # Task[:fast] { do_quick_task }
50
46
  #
51
- # @param executor [Concurrent::AbstractExecutorService,Symbol] Either an executor instance
52
- # or a name of predefined global
53
- # from concurrent-ruby
47
+ # @param executor [Concurrent::AbstractExecutorService,Symbol]
48
+ # Either an executor instance
49
+ # or a name of predefined global
50
+ # from concurrent-ruby
51
+ #
54
52
  # @return [Task]
55
53
  def [](executor, &block)
56
54
  new(Promise.execute(executor: executor, &block))
@@ -81,6 +79,7 @@ module Dry
81
79
  end
82
80
 
83
81
  include ConversionStubs[:to_maybe, :to_result]
82
+ extend ::Dry::Core::Deprecations[:"dry-monads"]
84
83
 
85
84
  # @api private
86
85
  attr_reader :promise
@@ -122,23 +121,23 @@ module Dry
122
121
  # and returns another task
123
122
  # @return [Task]
124
123
  def bind(&block)
125
- self.class.new(promise.flat_map { |value| block.(value).promise })
124
+ self.class.new(promise.flat_map { block.(_1).promise })
126
125
  end
127
- alias_method :then, :bind
126
+ deprecate :then, :bind
128
127
 
129
128
  # @return [String]
130
129
  def to_s
131
130
  state = case promise.state
132
131
  when :fulfilled
133
132
  if Unit.equal?(value!)
134
- 'value=()'
133
+ "value=()"
135
134
  else
136
135
  "value=#{value!.inspect}"
137
136
  end
138
137
  when :rejected
139
138
  "error=#{promise.reason.inspect}"
140
139
  else
141
- '?'
140
+ "?"
142
141
  end
143
142
 
144
143
  "Task(#{state})"
@@ -164,16 +163,14 @@ module Dry
164
163
  )
165
164
 
166
165
  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
166
+ inner = block.(v).promise
167
+ inner.execute
168
+ inner.on_success { child.on_fulfill(_1) }
169
+ inner.on_error { child.on_reject(_1) }
170
+ rescue StandardError => e
171
+ child.on_reject(e)
175
172
  end
176
- promise.on_success { |v| child.on_fulfill(v) }
173
+ promise.on_success { child.on_fulfill(_1) }
177
174
 
178
175
  self.class.new(child)
179
176
  end
@@ -236,9 +233,9 @@ module Dry
236
233
  #
237
234
  # @param val [Task]
238
235
  # @return [Task]
239
- def apply(val = Undefined)
240
- arg = Undefined.default(val) { yield }
241
- bind { |f| arg.fmap { |v| curry(f).(v) } }
236
+ def apply(val = Undefined, &block)
237
+ arg = Undefined.default(val, &block)
238
+ bind { |f| arg.fmap { curry(f).(_1) } }
242
239
  end
243
240
 
244
241
  # Maps a successful result to Unit, effectively discards it
@@ -267,8 +264,8 @@ module Dry
267
264
  # @api private
268
265
  def compare_promises(x, y)
269
266
  x.equal?(y) ||
270
- x.fulfilled? && y.fulfilled? && x.value == y.value ||
271
- x.rejected? && y.rejected? && x.reason == y.reason
267
+ (x.fulfilled? && y.fulfilled? && x.value == y.value) ||
268
+ (x.rejected? && y.rejected? && x.reason == y.reason)
272
269
  end
273
270
 
274
271
  # Task constructors.
@@ -314,7 +311,7 @@ module Dry
314
311
  end
315
312
  end
316
313
 
317
- require 'dry/monads/registry'
314
+ require "dry/monads/registry"
318
315
  register_mixin(:task, Task::Mixin)
319
316
  end
320
317
  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,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/monads/validated'
3
+ # rubocop:disable Naming/ConstantName
4
+ # rubocop:disable Style/MutableConstant
4
5
 
5
6
  module Dry
6
7
  module Monads
@@ -20,3 +21,6 @@ module Dry
20
21
  Traverse.freeze
21
22
  end
22
23
  end
24
+
25
+ # rubocop:enable Style/MutableConstant
26
+ # rubocop:enable Naming/ConstantName