dry-monads 0.3.1 → 0.4.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.
@@ -0,0 +1,15 @@
1
+ module Dry
2
+ module Monads
3
+ class UnwrapError < StandardError
4
+ def initialize(ctx)
5
+ super("value! was called on #{ ctx.inspect }")
6
+ end
7
+ end
8
+
9
+ class InvalidFailureTypeError < StandardError
10
+ def initialize(failure)
11
+ super("Cannot create Failure from #{ failure.inspect }, it doesn't meet constraints")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -25,7 +25,7 @@ module Dry
25
25
  elsif value.respond_to?(:to_ary)
26
26
  List.new(value.to_ary, type)
27
27
  else
28
- raise ArgumentError, "Can't coerce #{value.inspect} to List"
28
+ raise TypeError, "Can't coerce #{value.inspect} to List"
29
29
  end
30
30
  end
31
31
 
@@ -89,14 +89,14 @@ module Dry
89
89
  end
90
90
 
91
91
  # Maps a block over the list. Acts as `Array#map`.
92
- # Requires a block.
92
+ # Note that this method returns an Array instance, not a List
93
93
  #
94
- # @return [List]
94
+ # @return [List,Enumerator]
95
95
  def map(&block)
96
96
  if block
97
97
  fmap(block)
98
98
  else
99
- raise ArgumentError, "Missing block"
99
+ value.map(&block)
100
100
  end
101
101
  end
102
102
 
@@ -118,7 +118,8 @@ module Dry
118
118
  #
119
119
  # @return [String]
120
120
  def inspect
121
- "List#{ value.inspect }"
121
+ type_ann = typed? ? "<#{ type.name.split('::').last }>" : ''
122
+ "List#{ type_ann }#{ value.inspect }"
122
123
  end
123
124
  alias_method :to_s, :inspect
124
125
 
@@ -235,11 +236,11 @@ module Dry
235
236
 
236
237
  # Traverses the list with a block (or without it).
237
238
  # This methods "flips" List structure with the given monad (obtained from the type).
238
- # Note that traversing requires the list to be types.
239
+ # Note that traversing requires the list to be typed.
239
240
  # Also if a block given, its returning type must be equal list's type.
240
241
  #
241
242
  # @example
242
- # List<Either>[Right(1), Right(2)].traverse # => Right([1, 2])
243
+ # List<Result>[Success(1), Success(2)].traverse # => Success([1, 2])
243
244
  # List<Maybe>[Some(1), None, Some(3)].traverse # => None
244
245
  #
245
246
  # @return [Monad] Result is a monadic value
@@ -1,4 +1,6 @@
1
1
  require 'dry/equalizer'
2
+ require 'dry/core/deprecations'
3
+ require 'dry/core/constants'
2
4
 
3
5
  require 'dry/monads/right_biased'
4
6
  require 'dry/monads/transformer'
@@ -9,9 +11,10 @@ module Dry
9
11
  #
10
12
  # @api public
11
13
  class Maybe
12
- include Dry::Equalizer(:value)
13
14
  include Transformer
14
15
 
16
+ extend Dry::Core::Deprecations[:'dry-monads']
17
+
15
18
  class << self
16
19
  # Wraps the given value with into a Maybe object.
17
20
  #
@@ -39,11 +42,13 @@ module Dry
39
42
  def none?
40
43
  is_a?(None)
41
44
  end
45
+ alias_method :failure?, :none?
42
46
 
43
47
  # Returns true for an instance of a {Maybe::Some} monad.
44
48
  def some?
45
49
  is_a?(Some)
46
50
  end
51
+ alias_method :success?, :some?
47
52
 
48
53
  # Returns self, added to keep the interface compatible with that of Either monad types.
49
54
  #
@@ -64,10 +69,9 @@ module Dry
64
69
  #
65
70
  # @api public
66
71
  class Some < Maybe
72
+ include Dry::Equalizer(:value!)
67
73
  include RightBiased::Right
68
74
 
69
- attr_reader :value
70
-
71
75
  def initialize(value)
72
76
  raise ArgumentError, 'nil cannot be some' if value.nil?
73
77
  @value = value
@@ -89,9 +93,9 @@ module Dry
89
93
 
90
94
  # @return [String]
91
95
  def to_s
92
- "Some(#{value.inspect})"
96
+ "Some(#{ @value.inspect })"
93
97
  end
94
- alias inspect to_s
98
+ alias_method :inspect, :to_s
95
99
  end
96
100
 
97
101
  # Represents an absence of a value, i.e. the value nil.
@@ -100,14 +104,9 @@ module Dry
100
104
  class None < Maybe
101
105
  include RightBiased::Left
102
106
 
103
- @instance = new
107
+ @instance = new.freeze
104
108
  singleton_class.send(:attr_reader, :instance)
105
109
 
106
- # @return [nil]
107
- def value
108
- nil
109
- end
110
-
111
110
  # If a block is given passes internal value to it and returns the result,
112
111
  # otherwise simply returns the parameter val.
113
112
  #
@@ -144,7 +143,18 @@ module Dry
144
143
  def to_s
145
144
  'None'
146
145
  end
147
- alias inspect to_s
146
+ alias_method :inspect, :to_s
147
+
148
+ # @api private
149
+ def eql?(other)
150
+ other.is_a?(None)
151
+ end
152
+ alias_method :==, :eql?
153
+
154
+ # @api private
155
+ def hash
156
+ None.instance.object_id
157
+ end
148
158
  end
149
159
 
150
160
  # A module that can be included for easier access to Maybe monads.
@@ -153,22 +163,31 @@ module Dry
153
163
  Some = Some
154
164
  None = None
155
165
 
156
- # @param value [Object] the value to be stored in the monad
157
- # @return [Maybe::Some, Maybe::None]
158
- def Maybe(value)
159
- Maybe.lift(value)
160
- end
166
+ module Constructors
167
+ # @param value [Object] the value to be stored in the monad
168
+ # @return [Maybe::Some, Maybe::None]
169
+ def Maybe(value)
170
+ Maybe.lift(value)
171
+ end
161
172
 
162
- # @param value [Object] the value to be stored in the monad
163
- # @return [Maybe::Some]
164
- def Some(value)
165
- Some.new(value)
166
- end
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
182
+ end
167
183
 
168
- # @return [Maybe::None]
169
- def None
170
- None.instance
184
+ # @return [Maybe::None]
185
+ def None
186
+ None.instance
187
+ end
171
188
  end
189
+
190
+ include Constructors
172
191
  end
173
192
  end
174
193
  end
@@ -0,0 +1,250 @@
1
+ require 'dry/equalizer'
2
+ require 'dry/core/constants'
3
+
4
+ require 'dry/monads/right_biased'
5
+ require 'dry/monads/transformer'
6
+ require 'dry/monads/maybe'
7
+
8
+ module Dry
9
+ module Monads
10
+ # Represents an operation which either succeeded or failed.
11
+ #
12
+ # @api public
13
+ class Result
14
+ include Transformer
15
+
16
+ attr_reader :success, :failure
17
+
18
+ class << self
19
+ # Wraps the given value with Success
20
+ #
21
+ # @param value [Object] the value to be stored inside Success
22
+ # @return [Result::Success]
23
+ def pure(value)
24
+ Success.new(value)
25
+ end
26
+ end
27
+
28
+ # Returns self, added to keep the interface compatible with other monads.
29
+ #
30
+ # @return [Result::Success, Result::Failure]
31
+ def to_result
32
+ self
33
+ end
34
+ alias_method :to_either, :to_result
35
+
36
+ # Returns the Result monad.
37
+ # This is how we're doing polymorphism in Ruby 😕
38
+ #
39
+ # @return [Monad]
40
+ def monad
41
+ Result
42
+ end
43
+
44
+ # Represents a value of a successful operation.
45
+ #
46
+ # @api public
47
+ class Success < Result
48
+ include RightBiased::Right
49
+ include Dry::Equalizer(:value!)
50
+
51
+ alias_method :success, :value!
52
+
53
+ # @param value [Object] a value of a successful operation
54
+ def initialize(value)
55
+ @value = value
56
+ end
57
+
58
+ # Apply the second function to value.
59
+ #
60
+ # @api public
61
+ def result(_, f)
62
+ f.(@value)
63
+ end
64
+
65
+ # Returns false
66
+ def failure?
67
+ false
68
+ end
69
+ alias_method :left?, :failure?
70
+
71
+ # Returns true
72
+ def success?
73
+ true
74
+ end
75
+ alias_method :right?, :success?
76
+
77
+ # Does the same thing as #bind except it also wraps the value
78
+ # in an instance of Result::Success monad. This allows for easier
79
+ # chaining of calls.
80
+ #
81
+ # @example
82
+ # Dry::Monads.Success(4).fmap(&:succ).fmap(->(n) { n**2 }) # => Success(25)
83
+ #
84
+ # @param args [Array<Object>] arguments will be transparently passed through to #bind
85
+ # @return [Result::Success]
86
+ def fmap(*args, &block)
87
+ Success.new(bind(*args, &block))
88
+ end
89
+
90
+ # @return [String]
91
+ def to_s
92
+ "Success(#{ @value.inspect })"
93
+ end
94
+ alias_method :inspect, :to_s
95
+
96
+ # @return [Maybe::Some]
97
+ def to_maybe
98
+ Kernel.warn 'Success(nil) transformed to None' if @value.nil?
99
+ Dry::Monads::Maybe(@value)
100
+ end
101
+
102
+ # Transform to a Failure instance
103
+ #
104
+ # @return [Result::Failure]
105
+ def flip
106
+ Failure.new(@value)
107
+ end
108
+ end
109
+
110
+ # Represents a value of a failed operation.
111
+ #
112
+ # @api public
113
+ class Failure < Result
114
+ include RightBiased::Left
115
+ include Dry::Equalizer(:failure)
116
+
117
+ # @api private
118
+ def failure
119
+ @value
120
+ end
121
+ alias_method :left, :failure
122
+
123
+ # @param value [Object] a value in an error state
124
+ def initialize(value)
125
+ @value = value
126
+ end
127
+
128
+ # Apply the first function to value.
129
+ #
130
+ # @api public
131
+ def result(f, _)
132
+ f.(@value)
133
+ end
134
+
135
+ # Returns true
136
+ def failure?
137
+ true
138
+ end
139
+ alias_method :left?, :failure?
140
+
141
+ # Returns false
142
+ def success?
143
+ false
144
+ end
145
+ alias_method :right?, :success?
146
+
147
+ # If a block is given passes internal value to it and returns the result,
148
+ # otherwise simply returns the first argument.
149
+ #
150
+ # @example
151
+ # Dry::Monads.Failure(ArgumentError.new('error message')).or(&:message) # => "error message"
152
+ #
153
+ # @param args [Array<Object>] arguments that will be passed to a block
154
+ # if one was given, otherwise the first
155
+ # value will be returned
156
+ # @return [Object]
157
+ def or(*args)
158
+ if block_given?
159
+ yield(@value, *args)
160
+ else
161
+ args[0]
162
+ end
163
+ end
164
+
165
+ # A lifted version of `#or`. Wraps the passed value or the block result with Result::Success.
166
+ #
167
+ # @example
168
+ # Dry::Monads.Failure.new('no value').or_fmap('value') # => Success("value")
169
+ # Dry::Monads.Failure.new('no value').or_fmap { 'value' } # => Success("value")
170
+ #
171
+ # @param args [Array<Object>] arguments will be passed to the underlying `#or` call
172
+ # @return [Result::Success] Wrapped value
173
+ def or_fmap(*args, &block)
174
+ Success.new(self.or(*args, &block))
175
+ end
176
+
177
+ # @return [String]
178
+ def to_s
179
+ "Failure(#{ @value.inspect })"
180
+ end
181
+ alias_method :inspect, :to_s
182
+
183
+ # @return [Maybe::None]
184
+ def to_maybe
185
+ Maybe::None.instance
186
+ end
187
+
188
+ # Transform to a Success instance
189
+ #
190
+ # @return [Result::Success]
191
+ def flip
192
+ Success.new(@value)
193
+ end
194
+
195
+ # @see Dry::Monads::RightBiased::Left#value_or
196
+ def value_or(val = nil)
197
+ if block_given?
198
+ yield(@value)
199
+ else
200
+ val
201
+ end
202
+ end
203
+
204
+ # @param other [Result]
205
+ # @return [Boolean]
206
+ def ===(other)
207
+ Failure === other && failure === other.failure
208
+ end
209
+ end
210
+
211
+ # A module that can be included for easier access to Result monads.
212
+ module Mixin
213
+ Success = Dry::Monads::Result::Success
214
+ Failure = Dry::Monads::Result::Failure
215
+
216
+ module Constructors
217
+ # @param value [Object] the value to be stored in the monad
218
+ # @return [Result::Success]
219
+ def Success(value = Dry::Core::Constants::Undefined, &block)
220
+ if value.equal?(Dry::Core::Constants::Undefined)
221
+ raise ArgumentError, 'No value given' if block.nil?
222
+ Success.new(block)
223
+ else
224
+ Success.new(value)
225
+ end
226
+ end
227
+ alias_method :Right, :Success
228
+
229
+ # @param value [Object] the value to be stored in the monad
230
+ # @return [Result::Failure]
231
+ def Failure(value = Dry::Core::Constants::Undefined, &block)
232
+ if value.equal?(Dry::Core::Constants::Undefined)
233
+ raise ArgumentError, 'No value given' if block.nil?
234
+ Failure.new(block)
235
+ else
236
+ Failure.new(value)
237
+ end
238
+ end
239
+ alias_method :Left, :Failure
240
+ end
241
+
242
+ include Constructors
243
+ end
244
+ end
245
+
246
+ Either = Result
247
+ Result::Right = Result::Success
248
+ Result::Left = Result::Failure
249
+ end
250
+ end