dry-monads 0.4.0 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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