dry-monads 0.4.0 → 1.0.0.beta1

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.
@@ -1,14 +1,16 @@
1
1
  module Dry
2
2
  module Monads
3
+ # An unsuccessful result of extracting a value from a monad.
3
4
  class UnwrapError < StandardError
4
5
  def initialize(ctx)
5
6
  super("value! was called on #{ ctx.inspect }")
6
7
  end
7
8
  end
8
9
 
10
+ # An error thrown on returning a Failure of unknown type.
9
11
  class InvalidFailureTypeError < StandardError
10
12
  def initialize(failure)
11
- super("Cannot create Failure from #{ failure.inspect }, it doesn't meet constraints")
13
+ super("Cannot create Failure from #{ failure.inspect }, it doesn't meet the constraints")
12
14
  end
13
15
  end
14
16
  end
@@ -0,0 +1,78 @@
1
+ require 'concurrent/promise'
2
+
3
+ require 'dry/monads/task'
4
+
5
+ module Dry
6
+ module Monads
7
+ # Lazy is a twin of Task which is always executed on the current thread.
8
+ # The underlying mechanism provided by concurrent-ruby ensures the given
9
+ # computation is evaluated not more than once (compare with the built-in
10
+ # lazy assignement ||= which does not guarantee this).
11
+ class Lazy < Task
12
+ class << self
13
+ # @private
14
+ def new(promise = nil, &block)
15
+ if promise
16
+ super(promise)
17
+ else
18
+ super(Concurrent::Promise.new(executor: :immediate, &block))
19
+ end
20
+ end
21
+
22
+ private :[]
23
+ end
24
+
25
+ # Forces the compution and returns its value.
26
+ #
27
+ # @return [Object]
28
+ def value!
29
+ @promise.execute.value!
30
+ end
31
+ alias_method :force!, :value!
32
+
33
+ # Forces the computation. Note that if the computation
34
+ # thrown an error it won't be re-raised as opposed to value!/force!.
35
+ #
36
+ # @return [Lazy]
37
+ def force
38
+ @promise.execute
39
+ self
40
+ end
41
+
42
+ # @return [String]
43
+ def to_s
44
+ state = case promise.state
45
+ when :fulfilled
46
+ value!.inspect
47
+ when :rejected
48
+ "!#{ promise.reason.inspect }"
49
+ else
50
+ '?'
51
+ end
52
+
53
+ "Lazy(#{ state })"
54
+ end
55
+ alias_method :inspect, :to_s
56
+
57
+ # Lazy constructors
58
+ #
59
+ module Mixin
60
+ # @see Dry::Monads::Lazy
61
+ Lazy = Lazy
62
+
63
+ # Lazy constructors
64
+ module Constructors
65
+ # Lazy computation contructor
66
+ #
67
+ # @param block [Proc]
68
+ # @return [Lazy]
69
+ def Lazy(&block)
70
+ Lazy.new(&block)
71
+ end
72
+ end
73
+
74
+ include Constructors
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,9 +1,17 @@
1
1
  require 'dry/equalizer'
2
+ require 'dry/core/deprecations'
3
+
2
4
  require 'dry/monads/maybe'
5
+ require 'dry/monads/task'
6
+ require 'dry/monads/result'
7
+ require 'dry/monads/try'
8
+ require 'dry/monads/validated'
3
9
  require 'dry/monads/transformer'
10
+ require 'dry/monads/curry'
4
11
 
5
12
  module Dry
6
13
  module Monads
14
+ # The List monad.
7
15
  class List
8
16
  class << self
9
17
  # Builds a list.
@@ -23,7 +31,13 @@ module Dry
23
31
  if value.nil?
24
32
  List.new([], type)
25
33
  elsif value.respond_to?(:to_ary)
26
- List.new(value.to_ary, type)
34
+ values = value.to_ary
35
+
36
+ if !values.empty? && type.nil? && values[0].respond_to?(:monad)
37
+ List.new(values, values[0].monad)
38
+ else
39
+ List.new(values, type)
40
+ end
27
41
  else
28
42
  raise TypeError, "Can't coerce #{value.inspect} to List"
29
43
  end
@@ -33,11 +47,19 @@ module Dry
33
47
  #
34
48
  # @param value [Object] any object
35
49
  # @return [List]
36
- def pure(value, type = nil)
37
- new([value], type)
50
+ def pure(value = Undefined, type = nil, &block)
51
+ if value.equal?(Undefined)
52
+ new([block])
53
+ elsif block
54
+ new([block], value)
55
+ else
56
+ new([value], type)
57
+ end
38
58
  end
39
59
  end
40
60
 
61
+ extend Dry::Core::Deprecations[:'dry-monads']
62
+
41
63
  include Dry::Equalizer(:value, :type)
42
64
  include Transformer
43
65
 
@@ -200,7 +222,7 @@ module Dry
200
222
  #
201
223
  # @return [Maybe<Object>]
202
224
  def head
203
- Maybe.coerce(value.first)
225
+ Monads::Maybe.coerce(value.first)
204
226
  end
205
227
 
206
228
  # Returns list's tail.
@@ -218,8 +240,13 @@ module Dry
218
240
  def typed(type = nil)
219
241
  if type.nil?
220
242
  if size.zero?
221
- raise ArgumentError, "Cannot infer monad for an empty list"
243
+ raise ArgumentError, "Cannot infer a monad for an empty list"
222
244
  else
245
+ self.class.warn(
246
+ "Automatic monad inference is deprecated, pass a type explicitly "\
247
+ "or use a predefined constant, e.g. List::Result\n"\
248
+ "#{caller.find { |l| l !~ %r{(lib/dry/monads)|(gems)} }}"
249
+ )
223
250
  self.class.new(value, value[0].monad)
224
251
  end
225
252
  else
@@ -244,17 +271,28 @@ module Dry
244
271
  # List<Maybe>[Some(1), None, Some(3)].traverse # => None
245
272
  #
246
273
  # @return [Monad] Result is a monadic value
247
- def traverse
274
+ def traverse(proc = nil, &block)
248
275
  unless typed?
249
276
  raise StandardError, "Cannot traverse an untyped list"
250
277
  end
251
278
 
252
- foldl(type.pure(EMPTY)) { |acc, el|
253
- acc.bind { |unwrapped|
254
- mapped = block_given? ? yield(el) : el
255
- mapped.fmap { |i| unwrapped + List[i] }
256
- }
257
- }
279
+ cons = type.pure { |list, i| list + List.pure(i) }
280
+ with = proc || block || Traverse[type]
281
+
282
+ foldl(type.pure(EMPTY)) do |acc, el|
283
+ cons.
284
+ apply(acc).
285
+ apply { with.(el) }
286
+ end
287
+ end
288
+
289
+ # Applies the stored functions to the elements of the given list.
290
+ #
291
+ # @param list [List]
292
+ # @return [List]
293
+ def apply(list = Undefined)
294
+ v = Undefined.default(list) { yield }
295
+ fmap(Curry).bind { |f| v.fmap { |x| f.(x) } }
258
296
  end
259
297
 
260
298
  # Returns the List monad.
@@ -264,6 +302,13 @@ module Dry
264
302
  List
265
303
  end
266
304
 
305
+ # Returns self.
306
+ #
307
+ # @return [Result::Success, Result::Failure]
308
+ def to_monad
309
+ self
310
+ end
311
+
267
312
  private
268
313
 
269
314
  def coerce(other)
@@ -273,10 +318,60 @@ module Dry
273
318
  # Empty list
274
319
  EMPTY = List.new([].freeze).freeze
275
320
 
321
+ # @private
322
+ class ListBuilder
323
+ class << self
324
+ alias_method :[], :new
325
+ end
326
+
327
+ attr_reader :type
328
+
329
+ def initialize(type)
330
+ @type = type
331
+ end
332
+
333
+ def [](*args)
334
+ List.new(args, type)
335
+ end
336
+
337
+ def coerce(value)
338
+ List.coerce(value, type)
339
+ end
340
+
341
+ def pure(val = Undefined, &block)
342
+ value = Undefined.default(val, block)
343
+ List.pure(value, type)
344
+ end
345
+ end
346
+
347
+ # List of tasks
348
+ Task = ListBuilder[Task]
349
+
350
+ # List of results
351
+ Result = ListBuilder[Result]
352
+
353
+ # List of results
354
+ Maybe = ListBuilder[Maybe]
355
+
356
+ # List of results
357
+ Try = ListBuilder[Try]
358
+
359
+ # List of validation results
360
+ Validated = ListBuilder[Validated]
361
+
362
+ # List contructors.
363
+ #
364
+ # @api public
276
365
  module Mixin
366
+
367
+ # @see Dry::Monads::List
277
368
  List = List
369
+
370
+ # @see Dry::Monads::List
278
371
  L = List
279
372
 
373
+ # List constructor.
374
+ # @return [List]
280
375
  def List(value)
281
376
  List.coerce(value)
282
377
  end
@@ -284,3 +379,5 @@ module Dry
284
379
  end
285
380
  end
286
381
  end
382
+
383
+ require 'dry/monads/traverse'
@@ -1,6 +1,6 @@
1
1
  require 'dry/equalizer'
2
- require 'dry/core/deprecations'
3
2
  require 'dry/core/constants'
3
+ require 'dry/core/deprecations'
4
4
 
5
5
  require 'dry/monads/right_biased'
6
6
  require 'dry/monads/transformer'
@@ -13,12 +13,12 @@ module Dry
13
13
  class Maybe
14
14
  include Transformer
15
15
 
16
- extend Dry::Core::Deprecations[:'dry-monads']
17
-
18
16
  class << self
17
+ extend Dry::Core::Deprecations[:'dry-monads']
18
+
19
19
  # Wraps the given value with into a Maybe object.
20
20
  #
21
- # @param value [Object] the value to be stored in the monad
21
+ # @param value [Object] value to be stored in the monad
22
22
  # @return [Maybe::Some, Maybe::None]
23
23
  def coerce(value)
24
24
  if value.nil?
@@ -27,14 +27,15 @@ module Dry
27
27
  Some.new(value)
28
28
  end
29
29
  end
30
- alias_method :lift, :coerce
30
+ deprecate :lift, :coerce
31
31
 
32
32
  # Wraps the given value with `Some`.
33
33
  #
34
- # @param value [Object] the value to be stored inside Some
34
+ # @param value [Object] value to be wrapped with Some
35
+ # @param block [Object] block to be wrapped with Some
35
36
  # @return [Maybe::Some]
36
- def pure(value)
37
- Some.new(value)
37
+ def pure(value = Undefined, &block)
38
+ Some.new(Undefined.default(value, block))
38
39
  end
39
40
  end
40
41
 
@@ -57,6 +58,13 @@ module Dry
57
58
  self
58
59
  end
59
60
 
61
+ # Returns self.
62
+ #
63
+ # @return [Maybe::Some, Maybe::None]
64
+ def to_monad
65
+ self
66
+ end
67
+
60
68
  # Returns the Maybe monad.
61
69
  # This is how we're doing polymorphism in Ruby 😕
62
70
  #
@@ -85,10 +93,10 @@ module Dry
85
93
  # Dry::Monads.Some(4).fmap(&:succ).fmap(->(n) { n**2 }) # => Some(25)
86
94
  #
87
95
  # @param args [Array<Object>] arguments will be transparently passed through to #bind
88
- # @return [Maybe::Some, Maybe::None] Lifted result, i.e. nil will be mapped to None,
96
+ # @return [Maybe::Some, Maybe::None] Wrapped result, i.e. nil will be mapped to None,
89
97
  # other values will be wrapped with Some
90
98
  def fmap(*args, &block)
91
- self.class.lift(bind(*args, &block))
99
+ self.class.coerce(bind(*args, &block))
92
100
  end
93
101
 
94
102
  # @return [String]
@@ -107,6 +115,15 @@ module Dry
107
115
  @instance = new.freeze
108
116
  singleton_class.send(:attr_reader, :instance)
109
117
 
118
+ # Line where the value was constructed
119
+ #
120
+ # @return [String]
121
+ attr_reader :trace
122
+
123
+ def initialize(trace = RightBiased::Left.trace_caller)
124
+ @trace = trace
125
+ end
126
+
110
127
  # If a block is given passes internal value to it and returns the result,
111
128
  # otherwise simply returns the parameter val.
112
129
  #
@@ -125,7 +142,7 @@ module Dry
125
142
  end
126
143
  end
127
144
 
128
- # A lifted version of `#or`. Applies `Maybe.lift` to the passed value or
145
+ # A lifted version of `#or`. Applies `Maybe.coerce` to the passed value or
129
146
  # to the block result.
130
147
  #
131
148
  # @example
@@ -136,7 +153,7 @@ module Dry
136
153
  # @return [Maybe::Some, Maybe::None] Lifted `#or` result, i.e. nil will be mapped to None,
137
154
  # other values will be wrapped with Some
138
155
  def or_fmap(*args, &block)
139
- Maybe.lift(self.or(*args, &block))
156
+ Maybe.coerce(self.or(*args, &block))
140
157
  end
141
158
 
142
159
  # @return [String]
@@ -151,7 +168,7 @@ module Dry
151
168
  end
152
169
  alias_method :==, :eql?
153
170
 
154
- # @api private
171
+ # @private
155
172
  def hash
156
173
  None.instance.object_id
157
174
  end
@@ -159,31 +176,40 @@ module Dry
159
176
 
160
177
  # A module that can be included for easier access to Maybe monads.
161
178
  module Mixin
179
+ # @see Dry::Monads::Maybe
162
180
  Maybe = Maybe
181
+ # @see Maybe::Some
163
182
  Some = Some
183
+ # @see Maybe::None
164
184
  None = None
165
185
 
186
+ # @private
166
187
  module Constructors
167
188
  # @param value [Object] the value to be stored in the monad
168
189
  # @return [Maybe::Some, Maybe::None]
169
190
  def Maybe(value)
170
- Maybe.lift(value)
191
+ Maybe.coerce(value)
171
192
  end
172
193
 
173
- # @param value [Object] the value to be stored in the monad
174
- # @return [Maybe::Some]
175
- def Some(value = Dry::Core::Constants::Undefined, &block)
176
- if value.equal?(Dry::Core::Constants::Undefined)
177
- raise ArgumentError, 'No value given' if block.nil?
178
- Some.new(block)
179
- else
180
- Some.new(value)
181
- end
194
+ # Some constructor
195
+ #
196
+ # @overload Some(value)
197
+ # @param value [Object] any value except `nil`
198
+ # @return [Maybe::Some]
199
+ #
200
+ # @overload Some(&block)
201
+ # @param block [Proc] a block to be wrapped with Some
202
+ # @return [Maybe::Some]
203
+ #
204
+ def Some(value = Undefined, &block)
205
+ v = Undefined.default(value, block)
206
+ raise ArgumentError, 'No value given' if !value.nil? && v.nil?
207
+ Some.new(v)
182
208
  end
183
209
 
184
210
  # @return [Maybe::None]
185
211
  def None
186
- None.instance
212
+ None.new(RightBiased::Left.trace_caller)
187
213
  end
188
214
  end
189
215