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,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.