Skip to content

Commit

Permalink
Deprecate Rightiased::Left#value (fixes #42)
Browse files Browse the repository at this point in the history
It only exists to make dry-monads backward-compatible with the kleisli gem.
However, it's an obvious anti-pattern that allows for writing unreliable code
though the main purprose of the gem is making code more reliable

See #42 for details
  • Loading branch information
flash-gordon committed Aug 31, 2017
1 parent 464b7ad commit a22490e
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 69 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
/pkg/
/spec/reports/
/tmp/
log/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
* `Either#flip` inverts an `Either` value (flash-gordon)
* `List#map` called without a block returns an `Enumerator` object (flash-gordon)

## Deprecated

* Direct accessing `value` on right-biased monads has been deprecated, use the `value!` method instead. `value!` will raise an exception if it is called on a Left/None/Failure instance (flash-gordon)

[Compare v0.3.1...master](https://github.com/dry-rb/dry-monads/compare/v0.3.1...master)

# v0.3.1 2017-03-18
Expand Down
2 changes: 1 addition & 1 deletion dry-monads.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ['lib']
spec.required_ruby_version = ">= 2.2.0"
spec.add_dependency 'dry-equalizer'
spec.add_dependency 'dry-core'
spec.add_dependency 'dry-core', '~> 0.3', '>= 0.3.2'

spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rake'
Expand Down
38 changes: 21 additions & 17 deletions lib/dry/monads/either.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ module Monads
#
# @api public
class Either
include Dry::Equalizer(:right, :left)
include Transformer

attr_reader :right, :left
Expand Down Expand Up @@ -44,19 +43,20 @@ def monad
# @api public
class Right < Either
include RightBiased::Right
include Dry::Equalizer(:value!)

alias value right
alias right value!

# @param right [Object] a value in a correct state
def initialize(right)
@right = right
def initialize(value)
@value = value
end

# Apply the second function to value.
#
# @api public
def either(_, f)
f.call(value)
f.(@value)
end

# Returns false
Expand Down Expand Up @@ -86,21 +86,21 @@ def fmap(*args, &block)

# @return [String]
def to_s
"Right(#{value.inspect})"
"Right(#{ @value.inspect })"
end
alias inspect to_s

# @return [Maybe::Some]
def to_maybe
Kernel.warn 'Right(nil) transformed to None' if value.nil?
Dry::Monads::Maybe(value)
Kernel.warn 'Right(nil) transformed to None' if @value.nil?
Dry::Monads::Maybe(@value)
end

# Transform to a Left instance
#
# @returns [Either::Left]
def flip
Left.new(value)
Left.new(@value)
end
end

Expand All @@ -109,19 +109,23 @@ def flip
# @api public
class Left < Either
include RightBiased::Left
include Dry::Equalizer(:left)

alias value left
# @api private
def left
@value
end

# @param left [Object] a value in an error state
def initialize(left)
@left = left
def initialize(value)
@value = value
end

# Apply the first function to value.
#
# @api public
def either(f, _)
f.call(value)
f.(@value)
end

# Returns true
Expand All @@ -148,7 +152,7 @@ def right?
# @return [Object]
def or(*args)
if block_given?
yield(value, *args)
yield(@value, *args)
else
args[0]
end
Expand All @@ -168,7 +172,7 @@ def or_fmap(*args, &block)

# @return [String]
def to_s
"Left(#{value.inspect})"
"Left(#{ @value.inspect })"
end
alias inspect to_s

Expand All @@ -181,13 +185,13 @@ def to_maybe
#
# @returns [Either::Left]
def flip
Right.new(value)
Right.new(@value)
end

# @see Dry::Monads::RightBiased::Left#value_or
def value_or(val = nil)
if block_given?
yield(value)
yield(@value)
else
val
end
Expand Down
9 changes: 9 additions & 0 deletions lib/dry/monads/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Dry
module Monads
class UnwrapError < StandardError
def initialize(left)
super("value! was called on #{ left.inspect }")
end
end
end
end
27 changes: 17 additions & 10 deletions lib/dry/monads/maybe.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'dry/equalizer'
require 'dry/core/deprecations'

require 'dry/monads/right_biased'
require 'dry/monads/transformer'
Expand All @@ -9,9 +10,10 @@ module Monads
#
# @api public
class Maybe
include Dry::Equalizer(:value)
include Transformer

extend Dry::Core::Deprecations[:'dry-monads']

class << self
# Wraps the given value with into a Maybe object.
#
Expand Down Expand Up @@ -67,8 +69,7 @@ def monad
# @api public
class Some < Maybe
include RightBiased::Right

attr_reader :value
include Dry::Equalizer(:value!)

def initialize(value)
raise ArgumentError, 'nil cannot be some' if value.nil?
Expand All @@ -91,7 +92,7 @@ def fmap(*args, &block)

# @return [String]
def to_s
"Some(#{value.inspect})"
"Some(#{ @value.inspect })"
end
alias inspect to_s
end
Expand All @@ -102,14 +103,9 @@ def to_s
class None < Maybe
include RightBiased::Left

@instance = new
@instance = new.freeze
singleton_class.send(:attr_reader, :instance)

# @return [nil]
def value
nil
end

# If a block is given passes internal value to it and returns the result,
# otherwise simply returns the parameter val.
#
Expand Down Expand Up @@ -147,6 +143,17 @@ def to_s
'None'
end
alias inspect to_s

# @api private
def eql?(other)
other.is_a?(None)
end
alias == eql?

# @api private
def hash
nil.hash
end
end

# A module that can be included for easier access to Maybe monads.
Expand Down
39 changes: 31 additions & 8 deletions lib/dry/monads/right_biased.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
require 'dry/core/constants'
require 'dry/core/deprecations'

require 'dry/monads/errors'

module Dry
module Monads
module RightBiased
# @api public
module Right
include Dry::Core::Constants

attr_reader :value
extend Dry::Core::Deprecations[:'dry-monads']

# Unwraps the underlying value
#
# @return [Object]
def value!
@value
end

deprecate :value, :value!

# Calls the passed in Proc object with value stored in self
# and returns the result.
Expand All @@ -24,18 +37,18 @@ module Right
# @return [Object] result of calling proc or block on the internal value
def bind(*args, **kwargs)
if args.empty? && !kwargs.empty?
vargs, vkwargs = destructure(value)
vargs, vkwargs = destructure(@value)
kw = [kwargs.merge(vkwargs)]
else
vargs = [value]
vargs = [@value]
kw = kwargs.empty? ? EMPTY_ARRAY : [kwargs]
end

if block_given?
yield(*vargs, *args, *kw)
else
obj, *rest = args
obj.call(*vargs, *rest, *kw)
obj.(*vargs, *rest, *kw)
end
end

Expand Down Expand Up @@ -80,7 +93,7 @@ def or_fmap(*)
#
# @return [Object]
def value_or(_val = nil)
value
@value
end

# Applies the stored value to the given argument if the argument has type of Right,
Expand All @@ -97,8 +110,8 @@ def value_or(_val = nil)
#
# @return [RightBiased::Left,RightBiased::Right]
def apply(val)
unless value.respond_to?(:call)
raise TypeError, "Cannot apply #{ val.inspect } to #{ value.inspect }"
unless @value.respond_to?(:call)
raise TypeError, "Cannot apply #{ val.inspect } to #{ @value.inspect }"
end
val.fmap { |unwrapped| curry.(unwrapped) }
end
Expand All @@ -114,7 +127,7 @@ def destructure(*args, **kwargs)
def curry
@curried ||=
begin
func = value.is_a?(Proc) ? value : value.method(:call)
func = @value.is_a?(Proc) ? @value : @value.method(:call)
seq_args = func.parameters.count { |type, _| type == :req }
seq_args += 1 if func.parameters.any? { |type, _| type == :keyreq }

Expand All @@ -127,9 +140,19 @@ def curry
end
end

# @api public
module Left
extend Dry::Core::Deprecations[:'dry-monads']

attr_reader :value

deprecate :value, message: '.value is deprecated, use .value! instead'

# Raises an error on accessing internal value
def value!
raise UnwrapError.new(self)
end

# Ignores the input parameter and returns self. It exists to keep the interface
# identical to that of {RightBiased::Right}.
#
Expand Down
12 changes: 6 additions & 6 deletions lib/dry/monads/try.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def failure?
#
# @api public
class Success < Try
include Dry::Equalizer(:value, :catchable)
include Dry::Equalizer(:value!, :catchable)
include RightBiased::Right

attr_reader :catchable
Expand Down Expand Up @@ -95,17 +95,17 @@ def fmap(*args, &block)

# @return [Maybe]
def to_maybe
Dry::Monads::Maybe(value)
Dry::Monads::Maybe(@value)
end

# @return [Either::Right]
def to_either
Dry::Monads::Right(value)
Dry::Monads::Right(@value)
end

# @return [String]
def to_s
"Try::Success(#{value.inspect})"
"Try::Success(#{ @value.inspect })"
end
alias inspect to_s
end
Expand Down Expand Up @@ -134,7 +134,7 @@ def to_either

# @return [String]
def to_s
"Try::Failure(#{exception.class}: #{exception.message})"
"Try::Failure(#{ exception.class }: #{ exception.message })"
end
alias inspect to_s

Expand All @@ -150,7 +150,7 @@ def to_s
# @return [Object]
def or(*args)
if block_given?
yield(value, *args)
yield(exception, *args)
else
args[0]
end
Expand Down
2 changes: 1 addition & 1 deletion lib/json/add/dry/monads/maybe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def self.json_create(serialized)
def as_json(*)
{
JSON.create_id => self.class.name,
value: value
value: none? ? nil : @value
}
end

Expand Down
Loading

0 comments on commit a22490e

Please sign in to comment.