dry-monads 0.3.1 → 0.4.0

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