dry-monads 1.3.2 → 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 +157 -73
  3. data/LICENSE +1 -1
  4. data/README.md +18 -38
  5. data/dry-monads.gemspec +32 -30
  6. data/lib/dry-monads.rb +3 -1
  7. data/lib/dry/monads.rb +4 -2
  8. data/lib/dry/monads/all.rb +4 -2
  9. data/lib/dry/monads/constants.rb +1 -1
  10. data/lib/dry/monads/conversion_stubs.rb +2 -0
  11. data/lib/dry/monads/curry.rb +2 -0
  12. data/lib/dry/monads/do.rb +55 -17
  13. data/lib/dry/monads/do/all.rb +39 -17
  14. data/lib/dry/monads/do/mixin.rb +2 -0
  15. data/lib/dry/monads/either.rb +9 -7
  16. data/lib/dry/monads/errors.rb +8 -3
  17. data/lib/dry/monads/lazy.rb +19 -6
  18. data/lib/dry/monads/list.rb +31 -30
  19. data/lib/dry/monads/maybe.rb +90 -19
  20. data/lib/dry/monads/registry.rb +15 -12
  21. data/lib/dry/monads/result.rb +42 -15
  22. data/lib/dry/monads/result/fixed.rb +35 -24
  23. data/lib/dry/monads/right_biased.rb +45 -24
  24. data/lib/dry/monads/task.rb +25 -22
  25. data/lib/dry/monads/transformer.rb +4 -1
  26. data/lib/dry/monads/traverse.rb +9 -1
  27. data/lib/dry/monads/try.rb +51 -13
  28. data/lib/dry/monads/unit.rb +6 -2
  29. data/lib/dry/monads/validated.rb +27 -20
  30. data/lib/dry/monads/version.rb +3 -1
  31. data/lib/json/add/dry/monads/maybe.rb +4 -3
  32. metadata +27 -75
  33. data/.codeclimate.yml +0 -12
  34. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  35. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -34
  36. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  37. data/.github/workflows/ci.yml +0 -74
  38. data/.github/workflows/docsite.yml +0 -34
  39. data/.github/workflows/sync_configs.yml +0 -34
  40. data/.gitignore +0 -10
  41. data/.rspec +0 -4
  42. data/.rubocop.yml +0 -89
  43. data/.yardopts +0 -4
  44. data/CODE_OF_CONDUCT.md +0 -13
  45. data/CONTRIBUTING.md +0 -29
  46. data/Gemfile +0 -23
  47. data/Rakefile +0 -6
  48. data/bin/console +0 -16
  49. data/bin/setup +0 -7
  50. data/docsite/source/case-equality.html.md +0 -42
  51. data/docsite/source/do-notation.html.md +0 -207
  52. data/docsite/source/getting-started.html.md +0 -142
  53. data/docsite/source/index.html.md +0 -179
  54. data/docsite/source/list.html.md +0 -87
  55. data/docsite/source/maybe.html.md +0 -146
  56. data/docsite/source/pattern-matching.html.md +0 -68
  57. data/docsite/source/result.html.md +0 -190
  58. data/docsite/source/task.html.md +0 -126
  59. data/docsite/source/tracing-failures.html.md +0 -32
  60. data/docsite/source/try.html.md +0 -76
  61. data/docsite/source/unit.html.md +0 -36
  62. data/docsite/source/validated.html.md +0 -88
  63. data/log/.gitkeep +0 -0
@@ -1,16 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Monads
3
5
  # An unsuccessful result of extracting a value from a monad.
4
6
  class UnwrapError < StandardError
5
- def initialize(ctx)
6
- super("value! was called on #{ ctx.inspect }")
7
+ attr_reader :receiver
8
+
9
+ def initialize(receiver)
10
+ @receiver = receiver
11
+ super("value! was called on #{receiver.inspect}")
7
12
  end
8
13
  end
9
14
 
10
15
  # An error thrown on returning a Failure of unknown type.
11
16
  class InvalidFailureTypeError < StandardError
12
17
  def initialize(failure)
13
- super("Cannot create Failure from #{ failure.inspect }, it doesn't meet the constraints")
18
+ super("Cannot create Failure from #{failure.inspect}, it doesn't meet the constraints")
14
19
  end
15
20
  end
16
21
 
@@ -1,6 +1,9 @@
1
- require 'concurrent/promise'
1
+ # frozen_string_literal: true
2
2
 
3
- require 'dry/monads/task'
3
+ require "concurrent/promise"
4
+
5
+ require "dry/core/deprecations"
6
+ require "dry/monads/task"
4
7
 
5
8
  module Dry
6
9
  module Monads
@@ -9,6 +12,8 @@ module Dry
9
12
  # computation is evaluated not more than once (compare with the built-in
10
13
  # lazy assignement ||= which does not guarantee this).
11
14
  class Lazy < Task
15
+ extend ::Dry::Core::Deprecations[:"dry-monads"]
16
+
12
17
  class << self
13
18
  # @private
14
19
  def new(promise = nil, &block)
@@ -39,18 +44,26 @@ module Dry
39
44
  self
40
45
  end
41
46
 
47
+ # @return [Boolean]
48
+ def evaluated?
49
+ @promise.complete?
50
+ end
51
+ deprecate :complete?, :evaluated?
52
+
53
+ undef_method :wait
54
+
42
55
  # @return [String]
43
56
  def to_s
44
57
  state = case promise.state
45
58
  when :fulfilled
46
59
  value!.inspect
47
60
  when :rejected
48
- "!#{ promise.reason.inspect }"
61
+ "!#{promise.reason.inspect}"
49
62
  else
50
- '?'
63
+ "?"
51
64
  end
52
65
 
53
- "Lazy(#{ state })"
66
+ "Lazy(#{state})"
54
67
  end
55
68
  alias_method :inspect, :to_s
56
69
 
@@ -78,7 +91,7 @@ module Dry
78
91
  end
79
92
  end
80
93
 
81
- require 'dry/monads/registry'
94
+ require "dry/monads/registry"
82
95
  register_mixin(:lazy, Lazy::Mixin)
83
96
  end
84
97
  end
@@ -1,12 +1,14 @@
1
- require 'dry/equalizer'
1
+ # frozen_string_literal: true
2
2
 
3
- require 'dry/monads/maybe'
4
- require 'dry/monads/task'
5
- require 'dry/monads/result'
6
- require 'dry/monads/try'
7
- require 'dry/monads/validated'
8
- require 'dry/monads/transformer'
9
- require 'dry/monads/curry'
3
+ require "dry/core/equalizer"
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"
10
12
 
11
13
  module Dry
12
14
  module Monads
@@ -81,7 +83,7 @@ module Dry
81
83
  end
82
84
  end
83
85
 
84
- extend Dry::Core::Deprecations[:'dry-monads']
86
+ extend Dry::Core::Deprecations[:"dry-monads"]
85
87
 
86
88
  include Dry::Equalizer(:value, :type)
87
89
  include Transformer
@@ -134,14 +136,14 @@ module Dry
134
136
  end
135
137
 
136
138
  # Maps a block over the list. Acts as `Array#map`.
137
- # 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
138
140
  #
139
141
  # @return [List,Enumerator]
140
142
  def map(&block)
141
- if block
143
+ if block_given?
142
144
  fmap(block)
143
145
  else
144
- value.map(&block)
146
+ value.map
145
147
  end
146
148
  end
147
149
 
@@ -163,8 +165,8 @@ module Dry
163
165
  #
164
166
  # @return [String]
165
167
  def inspect
166
- type_ann = typed? ? "<#{ type.name.split('::').last }>" : ''
167
- "List#{ type_ann }#{ value.inspect }"
168
+ type_ann = typed? ? "<#{type.name.split("::").last}>" : ""
169
+ "List#{type_ann}#{value.inspect}"
168
170
  end
169
171
  alias_method :to_s, :inspect
170
172
 
@@ -190,8 +192,8 @@ module Dry
190
192
  #
191
193
  # @param initial [Object] Initial value
192
194
  # @return [Object]
193
- def fold_left(initial)
194
- value.reduce(initial) { |acc, v| yield(acc, v) }
195
+ def fold_left(initial, &block)
196
+ value.reduce(initial, &block)
195
197
  end
196
198
  alias_method :foldl, :fold_left
197
199
  alias_method :reduce, :fold_left
@@ -222,8 +224,8 @@ module Dry
222
224
  # Filters elements with a block
223
225
  #
224
226
  # @return [List]
225
- def filter
226
- coerce(value.select { |e| yield(e) })
227
+ def filter(&block)
228
+ coerce(value.select(&block))
227
229
  end
228
230
  alias_method :select, :filter
229
231
 
@@ -303,9 +305,9 @@ module Dry
303
305
  with = proc || block || Traverse[type]
304
306
 
305
307
  foldl(type.pure(EMPTY)) do |acc, el|
306
- cons.
307
- apply(acc).
308
- apply { with.(el) }
308
+ cons
309
+ .apply(acc)
310
+ .apply { with.(el) }
309
311
  end
310
312
  end
311
313
 
@@ -313,8 +315,8 @@ module Dry
313
315
  #
314
316
  # @param list [List]
315
317
  # @return [List]
316
- def apply(list = Undefined)
317
- v = Undefined.default(list) { yield }
318
+ def apply(list = Undefined, &block)
319
+ v = Undefined.default(list, &block)
318
320
  fmap(Curry).bind { |f| v.fmap { |x| f.(x) } }
319
321
  end
320
322
 
@@ -327,7 +329,7 @@ module Dry
327
329
 
328
330
  # Returns self.
329
331
  #
330
- # @return [Result::Success, Result::Failure]
332
+ # @return [List]
331
333
  def to_monad
332
334
  self
333
335
  end
@@ -343,7 +345,7 @@ module Dry
343
345
  # Some(n / divisor)
344
346
  # end
345
347
  # end
346
- # # => List[4, 2]
348
+ # # => List[2, 4]
347
349
  #
348
350
  # @example without block
349
351
  # List[Some(5), None(), Some(3)].collect.map { |x| x * 2 }
@@ -420,10 +422,10 @@ module Dry
420
422
  # List of results
421
423
  Result = ListBuilder[Result]
422
424
 
423
- # List of results
425
+ # List of maybes
424
426
  Maybe = ListBuilder[Maybe]
425
427
 
426
- # List of results
428
+ # List of tries
427
429
  Try = ListBuilder[Try]
428
430
 
429
431
  # List of validation results
@@ -433,7 +435,6 @@ module Dry
433
435
  #
434
436
  # @api public
435
437
  module Mixin
436
-
437
438
  # @see Dry::Monads::List
438
439
  List = List
439
440
 
@@ -448,9 +449,9 @@ module Dry
448
449
  end
449
450
  end
450
451
 
451
- require 'dry/monads/registry'
452
+ require "dry/monads/registry"
452
453
  register_mixin(:list, List::Mixin)
453
454
  end
454
455
  end
455
456
 
456
- require 'dry/monads/traverse'
457
+ require "dry/monads/traverse"
@@ -1,10 +1,13 @@
1
- require 'dry/equalizer'
2
- require 'dry/core/deprecations'
1
+ # frozen_string_literal: true
3
2
 
4
- require 'dry/monads/right_biased'
5
- require 'dry/monads/transformer'
6
- require 'dry/monads/unit'
7
- require 'dry/monads/constants'
3
+ require "dry/core/equalizer"
4
+ require "dry/core/deprecations"
5
+ require "dry/core/class_attributes"
6
+
7
+ require "dry/monads/right_biased"
8
+ require "dry/monads/transformer"
9
+ require "dry/monads/unit"
10
+ require "dry/monads/constants"
8
11
 
9
12
  module Dry
10
13
  module Monads
@@ -13,9 +16,13 @@ module Dry
13
16
  # @api public
14
17
  class Maybe
15
18
  include Transformer
19
+ extend Core::ClassAttributes
20
+
21
+ defines :warn_on_implicit_nil_coercion
22
+ warn_on_implicit_nil_coercion true
16
23
 
17
24
  class << self
18
- extend Core::Deprecations[:'dry-monads']
25
+ extend Core::Deprecations[:"dry-monads"]
19
26
 
20
27
  # Wraps the given value with into a Maybe object.
21
28
  #
@@ -103,7 +110,10 @@ module Dry
103
110
  end
104
111
 
105
112
  def initialize(value = Undefined)
106
- raise ArgumentError, 'nil cannot be some' if value.nil?
113
+ raise ArgumentError, "nil cannot be some" if value.nil?
114
+
115
+ super()
116
+
107
117
  @value = Undefined.default(value, Unit)
108
118
  end
109
119
 
@@ -118,14 +128,65 @@ module Dry
118
128
  # @return [Maybe::Some, Maybe::None] Wrapped result, i.e. nil will be mapped to None,
119
129
  # other values will be wrapped with Some
120
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)
121
164
  Maybe.coerce(bind(*args, &block))
122
165
  end
123
- 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
124
185
 
125
186
  # @return [String]
126
187
  def to_s
127
188
  if Unit.equal?(@value)
128
- 'Some()'
189
+ "Some()"
129
190
  else
130
191
  "Some(#{@value.inspect})"
131
192
  end
@@ -143,7 +204,7 @@ module Dry
143
204
  singleton_class.send(:attr_reader, :instance)
144
205
 
145
206
  # @api private
146
- def self.method_missing(m, *)
207
+ def self.method_missing(m, *) # rubocop:disable Style/MissingRespondToMissing
147
208
  if (instance.methods(true) - methods(true)).include?(m)
148
209
  raise ConstructorNotAppliedError.new(m, :None)
149
210
  else
@@ -158,6 +219,7 @@ module Dry
158
219
  attr_reader :trace
159
220
 
160
221
  def initialize(trace = RightBiased::Left.trace_caller)
222
+ super()
161
223
  @trace = trace
162
224
  end
163
225
 
@@ -199,7 +261,7 @@ module Dry
199
261
 
200
262
  # @return [String]
201
263
  def to_s
202
- 'None'
264
+ "None"
203
265
  end
204
266
  alias_method :inspect, :to_s
205
267
 
@@ -227,6 +289,13 @@ module Dry
227
289
  def deconstruct
228
290
  EMPTY_ARRAY
229
291
  end
292
+
293
+ # @see Maybe::Some#filter
294
+ #
295
+ # @return [Maybe::None]
296
+ def filter(_ = Undefined)
297
+ self
298
+ end
230
299
  end
231
300
 
232
301
  # A module that can be included for easier access to Maybe monads.
@@ -298,9 +367,9 @@ module Dry
298
367
  # None values are removed
299
368
  #
300
369
  # @example
301
- # Maybe::Hash.filter(foo: Some(1), bar: Some(2)) # => Some(foo: 1, bar: 2)
302
- # Maybe::Hash.filter(foo: Some(1), bar: None()) # => None()
303
- # 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 }
304
373
  #
305
374
  # @param hash [::Hash<Object,Maybe>]
306
375
  # @return [::Hash]
@@ -322,10 +391,12 @@ module Dry
322
391
 
323
392
  class Result
324
393
  class Success < Result
325
- # @return [Maybe::Some]
394
+ extend Core::Deprecations[:"dry-monads"]
395
+
396
+ # @return [Maybe]
326
397
  def to_maybe
327
- Kernel.warn 'Success(nil) transformed to None' if @value.nil?
328
- Dry::Monads::Maybe(@value)
398
+ warn "Success(nil) transformed to None" if @value.nil?
399
+ ::Dry::Monads::Maybe(@value)
329
400
  end
330
401
  end
331
402
 
@@ -386,7 +457,7 @@ module Dry
386
457
  end
387
458
  end
388
459
 
389
- require 'dry/monads/registry'
460
+ require "dry/monads/registry"
390
461
  register_mixin(:maybe, Maybe::Mixin)
391
462
  end
392
463
  end
@@ -1,4 +1,6 @@
1
- require 'concurrent/map'
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
2
4
 
3
5
  module Dry
4
6
  # Common, idiomatic monads for Ruby
@@ -8,16 +10,16 @@ module Dry
8
10
  @registry = {}
9
11
  @constructors = nil
10
12
  @paths = {
11
- do: 'dry/monads/do/all',
12
- lazy: 'dry/monads/lazy',
13
- list: 'dry/monads/list',
14
- maybe: 'dry/monads/maybe',
15
- task: 'dry/monads/task',
16
- try: 'dry/monads/try',
17
- 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",
18
20
  result: [
19
- 'dry/monads/result',
20
- 'dry/monads/result/fixed'
21
+ "dry/monads/result",
22
+ "dry/monads/result/fixed"
21
23
  ]
22
24
  }.freeze
23
25
  @mixins = Concurrent::Map.new
@@ -36,8 +38,9 @@ module Dry
36
38
  # @private
37
39
  def register_mixin(name, mod)
38
40
  if registry.key?(name)
39
- raise ArgumentError, "#{ name.inspect } is already registered"
41
+ raise ArgumentError, "#{name.inspect} is already registered"
40
42
  end
43
+
41
44
  self.registry = registry.merge(name => mod)
42
45
  end
43
46
 
@@ -49,7 +52,7 @@ module Dry
49
52
  # @private
50
53
  def load_monad(name)
51
54
  path = @paths.fetch(name) {
52
- raise ArgumentError, "#{ name.inspect } is not a known monad"
55
+ raise ArgumentError, "#{name.inspect} is not a known monad"
53
56
  }
54
57
  Array(path).each { |p| require p }
55
58
  end