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,34 @@
1
+ module Dry::Monads
2
+ class Result
3
+ # @see Monads#Result
4
+ # @api private
5
+ class Fixed < Module
6
+ def self.[](error, **options)
7
+ new(error, **options)
8
+ end
9
+
10
+ def initialize(error, **options)
11
+ @mod = Module.new do
12
+ define_method(:Failure) do |value|
13
+ if error === value
14
+ Failure.new(value)
15
+ else
16
+ raise InvalidFailureTypeError.new(value)
17
+ end
18
+ end
19
+
20
+ def Success(value)
21
+ Success.new(value)
22
+ end
23
+ end
24
+ end
25
+
26
+ # @api private
27
+ def included(base)
28
+ super
29
+
30
+ base.include(@mod)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,12 +1,25 @@
1
1
  require 'dry/core/constants'
2
+ require 'dry/core/deprecations'
3
+
4
+ require 'dry/monads/errors'
2
5
 
3
6
  module Dry
4
7
  module Monads
5
8
  module RightBiased
9
+ # @api public
6
10
  module Right
7
11
  include Dry::Core::Constants
8
12
 
9
- attr_reader :value
13
+ extend Dry::Core::Deprecations[:'dry-monads']
14
+
15
+ # Unwraps the underlying value
16
+ #
17
+ # @return [Object]
18
+ def value!
19
+ @value
20
+ end
21
+
22
+ deprecate :value, :value!
10
23
 
11
24
  # Calls the passed in Proc object with value stored in self
12
25
  # and returns the result.
@@ -24,10 +37,10 @@ module Dry
24
37
  # @return [Object] result of calling proc or block on the internal value
25
38
  def bind(*args, **kwargs)
26
39
  if args.empty? && !kwargs.empty?
27
- vargs, vkwargs = destructure(value)
40
+ vargs, vkwargs = destructure(@value)
28
41
  kw = [kwargs.merge(vkwargs)]
29
42
  else
30
- vargs = [value]
43
+ vargs = [@value]
31
44
  kw = kwargs.empty? ? EMPTY_ARRAY : [kwargs]
32
45
  end
33
46
 
@@ -35,7 +48,7 @@ module Dry
35
48
  yield(*vargs, *args, *kw)
36
49
  else
37
50
  obj, *rest = args
38
- obj.call(*vargs, *rest, *kw)
51
+ obj.(*vargs, *rest, *kw)
39
52
  end
40
53
  end
41
54
 
@@ -80,7 +93,33 @@ module Dry
80
93
  #
81
94
  # @return [Object]
82
95
  def value_or(_val = nil)
83
- value
96
+ @value
97
+ end
98
+
99
+ # Applies the stored value to the given argument if the argument has type of Right,
100
+ # otherwise returns the argument
101
+ #
102
+ # @example happy path
103
+ # create_user = Dry::Monads::Right(CreateUser.new)
104
+ # name = Right("John")
105
+ # create_user.apply(name) # equivalent to CreateUser.new.call("John")
106
+ #
107
+ # @example unhappy path
108
+ # name = Left(:name_missing)
109
+ # create_user.apply(name) # => Left(:name_missing)
110
+ #
111
+ # @return [RightBiased::Left,RightBiased::Right]
112
+ def apply(val)
113
+ unless @value.respond_to?(:call)
114
+ raise TypeError, "Cannot apply #{ val.inspect } to #{ @value.inspect }"
115
+ end
116
+ val.fmap { |unwrapped| curry.(unwrapped) }
117
+ end
118
+
119
+ # @param other [RightBiased]
120
+ # @return [Boolean]
121
+ def ===(other)
122
+ self.class == other.class && value! === other.value!
84
123
  end
85
124
 
86
125
  private
@@ -89,11 +128,37 @@ module Dry
89
128
  def destructure(*args, **kwargs)
90
129
  [args, kwargs]
91
130
  end
131
+
132
+ # @api private
133
+ def curry
134
+ @curried ||=
135
+ begin
136
+ func = @value.is_a?(Proc) ? @value : @value.method(:call)
137
+ seq_args = func.parameters.count { |type, _| type == :req }
138
+ seq_args += 1 if func.parameters.any? { |type, _| type == :keyreq }
139
+
140
+ if seq_args > 1
141
+ func.curry
142
+ else
143
+ func
144
+ end
145
+ end
146
+ end
92
147
  end
93
148
 
149
+ # @api public
94
150
  module Left
151
+ extend Dry::Core::Deprecations[:'dry-monads']
152
+
95
153
  attr_reader :value
96
154
 
155
+ deprecate :value, message: '.value is deprecated, use .value! instead'
156
+
157
+ # Raises an error on accessing internal value
158
+ def value!
159
+ raise UnwrapError.new(self)
160
+ end
161
+
97
162
  # Ignores the input parameter and returns self. It exists to keep the interface
98
163
  # identical to that of {RightBiased::Right}.
99
164
  #
@@ -143,7 +208,7 @@ module Dry
143
208
 
144
209
  # Returns the passed value
145
210
  #
146
- # @returns [Object]
211
+ # @return [Object]
147
212
  def value_or(val = nil)
148
213
  if block_given?
149
214
  yield
@@ -151,6 +216,14 @@ module Dry
151
216
  val
152
217
  end
153
218
  end
219
+
220
+ # Ignores the input parameter and returns self. It exists to keep the interface
221
+ # identical to that of {RightBiased::Right}.
222
+ #
223
+ # @return [RightBiased::Left]
224
+ def apply(*)
225
+ self
226
+ end
154
227
  end
155
228
  end
156
229
  end
@@ -1,6 +1,8 @@
1
1
  require 'dry/equalizer'
2
2
 
3
3
  require 'dry/monads/right_biased'
4
+ require 'dry/monads/result'
5
+ require 'dry/monads/maybe'
4
6
 
5
7
  module Dry
6
8
  module Monads
@@ -12,40 +14,37 @@ module Dry
12
14
  attr_reader :exception
13
15
 
14
16
  # Calls the passed in proc object and if successful stores the result in a
15
- # {Try::Success} monad, but if one of the specified exceptions was raised it stores
16
- # it in a {Try::Failure} monad.
17
+ # {Try::Value} monad, but if one of the specified exceptions was raised it stores
18
+ # it in a {Try::Error} monad.
17
19
  #
18
20
  # @param exceptions [Array<Exception>] list of exceptions to be rescued
19
21
  # @param f [Proc] the proc to be called
20
- # @return [Try::Success, Try::Failure]
22
+ # @return [Try::Value, Try::Error]
21
23
  def self.lift(exceptions, f)
22
- Success.new(exceptions, f.call)
24
+ Value.new(exceptions, f.call)
23
25
  rescue *exceptions => e
24
- Failure.new(e)
26
+ Error.new(e)
25
27
  end
26
28
 
27
- # Returns true for an instance of a {Try::Success} monad.
28
- def success?
29
- is_a? Success
29
+ # Returns true for an instance of a {Try::Value} monad.
30
+ def value?
31
+ is_a?(Value)
30
32
  end
33
+ alias_method :success?, :value?
31
34
 
32
- # Returns true for an instance of a {Try::Failure} monad.
33
- def failure?
34
- is_a? Failure
35
+ # Returns true for an instance of a {Try::Error} monad.
36
+ def error?
37
+ is_a?(Error)
35
38
  end
39
+ alias_method :failure?, :error?
36
40
 
37
41
  # Represents a result of a successful execution.
38
42
  #
39
43
  # @api public
40
- class Success < Try
41
- include Dry::Equalizer(:value, :catchable)
44
+ class Value < Try
45
+ include Dry::Equalizer(:value!, :catchable)
42
46
  include RightBiased::Right
43
47
 
44
- # Using #or is not a good practice, you should process exceptions
45
- # explicitly hence we don't offer an easy way to ignore them.
46
- # Use Try#to_maybe if you're sure you need `#or` for `Try`.
47
- undef :or
48
-
49
48
  attr_reader :catchable
50
49
 
51
50
  # @param exceptions [Array<Exception>] list of exceptions to be rescued
@@ -55,7 +54,7 @@ module Dry
55
54
  @value = value
56
55
  end
57
56
 
58
- alias bind_call bind
57
+ alias_method :bind_call, :bind
59
58
  private :bind_call
60
59
 
61
60
  # Calls the passed in Proc object with value stored in self
@@ -64,20 +63,20 @@ module Dry
64
63
  # If proc is nil, it expects a block to be given and will yield to it.
65
64
  #
66
65
  # @example
67
- # success = Dry::Monads::Try::Success.new(ZeroDivisionError, 10)
66
+ # success = Dry::Monads::Try::Value.new(ZeroDivisionError, 10)
68
67
  # success.bind(->(n) { n / 2 }) # => 5
69
- # success.bind { |n| n / 0 } # => Try::Failure(ZeroDivisionError: divided by 0)
68
+ # success.bind { |n| n / 0 } # => Try::Error(ZeroDivisionError: divided by 0)
70
69
  #
71
70
  # @param args [Array<Object>] arguments that will be passed to a block
72
71
  # if one was given, otherwise the first
73
72
  # value assumed to be a Proc (callable)
74
73
  # object and the rest of args will be passed
75
74
  # to this object along with the internal value
76
- # @return [Object, Try::Failure]
77
- def bind(*)
75
+ # @return [Object, Try::Error]
76
+ def bind(*args)
78
77
  super
79
78
  rescue *catchable => e
80
- Failure.new(e)
79
+ Error.new(e)
81
80
  end
82
81
 
83
82
  # Does the same thing as #bind except it also wraps the value
@@ -85,43 +84,43 @@ module Dry
85
84
  # chaining of calls.
86
85
  #
87
86
  # @example
88
- # success = Dry::Monads::Try::Success.new(ZeroDivisionError, 10)
87
+ # success = Dry::Monads::Try::Value.new(ZeroDivisionError, 10)
89
88
  # success.fmap(&:succ).fmap(&:succ).value # => 12
90
89
  # success.fmap(&:succ).fmap { |n| n / 0 }.fmap(&:succ).value # => nil
91
90
  #
92
91
  # @param args [Array<Object>] extra arguments for the block, arguments are being processes
93
92
  # just as in #bind
94
- # @return [Try::Success, Try::Failure]
93
+ # @return [Try::Value, Try::Error]
95
94
  def fmap(*args, &block)
96
- Success.new(catchable, bind_call(*args, &block))
95
+ Value.new(catchable, bind_call(*args, &block))
97
96
  rescue *catchable => e
98
- Failure.new(e)
97
+ Error.new(e)
99
98
  end
100
99
 
101
100
  # @return [Maybe]
102
101
  def to_maybe
103
- Dry::Monads::Maybe(value)
102
+ Dry::Monads::Maybe(@value)
104
103
  end
105
104
 
106
- # @return [Either::Right]
107
- def to_either
108
- Dry::Monads::Right(value)
105
+ # @return [Result::Success]
106
+ def to_result
107
+ Dry::Monads::Success(@value)
109
108
  end
109
+ alias_method :to_either, :to_result
110
110
 
111
111
  # @return [String]
112
112
  def to_s
113
- "Try::Success(#{value.inspect})"
113
+ "Try::Value(#{ @value.inspect })"
114
114
  end
115
- alias inspect to_s
115
+ alias_method :inspect, :to_s
116
116
  end
117
117
 
118
118
  # Represents a result of a failed execution.
119
119
  #
120
120
  # @api public
121
- class Failure < Try
121
+ class Error < Try
122
122
  include Dry::Equalizer(:exception)
123
123
  include RightBiased::Left
124
- undef :or
125
124
 
126
125
  # @param exception [Exception]
127
126
  def initialize(exception)
@@ -133,16 +132,41 @@ module Dry
133
132
  Dry::Monads::None()
134
133
  end
135
134
 
136
- # @return [Either::Left]
137
- def to_either
138
- Dry::Monads::Left(exception)
135
+ # @return [Result::Failure]
136
+ def to_result
137
+ Dry::Monads::Failure(exception)
139
138
  end
139
+ alias_method :to_either, :to_result
140
140
 
141
141
  # @return [String]
142
142
  def to_s
143
- "Try::Failure(#{exception.class}: #{exception.message})"
143
+ "Try::Error(#{ exception.class }: #{ exception.message })"
144
+ end
145
+ alias_method :inspect, :to_s
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
+ # Try(ZeroDivisionError) { 1 / 0 }.or { "zero!" } # => "zero!"
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(exception, *args)
160
+ else
161
+ args[0]
162
+ end
163
+ end
164
+
165
+ # @param other [Try]
166
+ # @return [Boolean]
167
+ def ===(other)
168
+ Error === other && exception === other.exception
144
169
  end
145
- alias inspect to_s
146
170
  end
147
171
 
148
172
  # A module that can be included for easier access to Try monads.
@@ -165,14 +189,34 @@ module Dry
165
189
 
166
190
  DEFAULT_EXCEPTIONS = [StandardError].freeze
167
191
 
168
- # A convenience wrapper for {Try.lift}.
192
+ # A convenience wrapper for {Monads::Try.lift}.
169
193
  # If no exceptions are provided it falls back to StandardError.
170
194
  # In general, relying on this behaviour is not recommended as it can lead to unnoticed
171
195
  # bugs and it is always better to explicitly specify a list of exceptions if possible.
196
+ #
197
+ # @param exceptions [Array<Exception>]
172
198
  def Try(*exceptions, &f)
173
199
  catchable = exceptions.empty? ? DEFAULT_EXCEPTIONS : exceptions.flatten
174
200
  Try.lift(catchable, f)
175
201
  end
202
+
203
+ def Value(value = Undefined, exceptions = DEFAULT_EXCEPTIONS, &block)
204
+ if value.equal?(Undefined)
205
+ raise ArgumentError, 'No value given' if block.nil?
206
+ Try::Value.new(exceptions, block)
207
+ else
208
+ Try::Value.new(exceptions, value)
209
+ end
210
+ end
211
+
212
+ def Error(error = Undefined, &block)
213
+ if error.equal?(Undefined)
214
+ raise ArgumentError, 'No value given' if block.nil?
215
+ Try::Error.new(block)
216
+ else
217
+ Try::Error.new(error)
218
+ end
219
+ end
176
220
  end
177
221
  end
178
222
  end
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Monads
3
- VERSION = '0.3.1'.freeze
3
+ VERSION = '0.4.0'.freeze
4
4
  end
5
5
  end
@@ -19,7 +19,7 @@ module Dry
19
19
  def as_json(*)
20
20
  {
21
21
  JSON.create_id => self.class.name,
22
- value: value
22
+ value: none? ? nil : @value
23
23
  }
24
24
  end
25
25
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-monads
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikita Shilnikov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-17 00:00:00.000000000 Z
11
+ date: 2017-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-equalizer
@@ -28,16 +28,22 @@ dependencies:
28
28
  name: dry-core
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
31
34
  - - ">="
32
35
  - !ruby/object:Gem::Version
33
- version: '0'
36
+ version: 0.3.3
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '0.3'
38
44
  - - ">="
39
45
  - !ruby/object:Gem::Version
40
- version: '0'
46
+ version: 0.3.3
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: bundler
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +86,20 @@ dependencies:
80
86
  - - ">="
81
87
  - !ruby/object:Gem::Version
82
88
  version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: dry-types
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0.12'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0.12'
83
103
  description: Common monads for Ruby.
84
104
  email:
85
105
  - fg@flashgordon.ru
@@ -91,7 +111,9 @@ files:
91
111
  - ".gitignore"
92
112
  - ".rspec"
93
113
  - ".travis.yml"
114
+ - ".yardopts"
94
115
  - CHANGELOG.md
116
+ - CONTRIBUTING.md
95
117
  - Gemfile
96
118
  - LICENSE
97
119
  - README.md
@@ -102,13 +124,17 @@ files:
102
124
  - lib/dry-monads.rb
103
125
  - lib/dry/monads.rb
104
126
  - lib/dry/monads/either.rb
127
+ - lib/dry/monads/errors.rb
105
128
  - lib/dry/monads/list.rb
106
129
  - lib/dry/monads/maybe.rb
130
+ - lib/dry/monads/result.rb
131
+ - lib/dry/monads/result/fixed.rb
107
132
  - lib/dry/monads/right_biased.rb
108
133
  - lib/dry/monads/transformer.rb
109
134
  - lib/dry/monads/try.rb
110
135
  - lib/dry/monads/version.rb
111
136
  - lib/json/add/dry/monads/maybe.rb
137
+ - log/.gitkeep
112
138
  homepage: https://github.com/dry-rb/dry-monads
113
139
  licenses:
114
140
  - MIT
@@ -122,7 +148,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
148
  requirements:
123
149
  - - ">="
124
150
  - !ruby/object:Gem::Version
125
- version: 2.1.0
151
+ version: 2.2.0
126
152
  required_rubygems_version: !ruby/object:Gem::Requirement
127
153
  requirements:
128
154
  - - ">="
@@ -130,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
156
  version: '0'
131
157
  requirements: []
132
158
  rubyforge_project:
133
- rubygems_version: 2.6.10
159
+ rubygems_version: 2.6.11
134
160
  signing_key:
135
161
  specification_version: 4
136
162
  summary: Common monads for Ruby.