dry-monads 1.3.5 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +161 -80
  3. data/LICENSE +1 -1
  4. data/README.md +5 -4
  5. data/dry-monads.gemspec +30 -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 -68
  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