module Fear::Try

The Try represents a computation that may either result in an exception, or return a successfully computed value. Instances of Try, are either an instance of Success or Failure.

For example, Try can be used to perform division on a user-defined input, without the need to do explicit exception-handling in all of the places that an exception might occur.

@example

include Fear::Try::Mixin

dividend = Fear.try { Integer(params[:dividend]) }
divisor = Fear.try { Integer(params[:divisor]) }
problem = dividend.flat_map { |x| divisor.map { |y| x / y } }

problem.match |m|
  m.success do |result|
    puts "Result of #{dividend.get} / #{divisor.get} is: #{result}"
  end

  m.failure(ZeroDivisionError) do
    puts "Division by zero is not allowed"
  end

  m.failure do |exception|
    puts "You entered something wrong. Try again"
    puts "Info from the exception: #{exception.message}"
  end
end

An important property of Try shown in the above example is its ability to pipeline, or chain, operations, catching exceptions along the way. The flat_map and map combinators in the above example each essentially pass off either their successfully completed value, wrapped in the Success type for it to be further operated upon by the next combinator in the chain, or the exception wrapped in the Failure type usually to be simply passed on down the chain. Combinators such as recover_with and recover are designed to provide some type of default behavior in the case of failure.

@note only non-fatal exceptions are caught by the combinators on Try.

Serious system errors, on the other hand, will be thrown.

@note all Try combinators will catch exceptions and return failure unless

otherwise specified in the documentation.

@!method get_or_else(*args)

Returns the value from this +Success+ or evaluates the given
default argument if this is a +Failure+.
@overload get_or_else(&default)
  @yieldreturn [any]
  @return [any]
  @example
    Fear.success(42).get_or_else { 24/2 }                #=> 42
    Fear.failure(ArgumentError.new).get_or_else { 24/2 } #=> 12
@overload get_or_else(default)
  @return [any]
  @example
    Fear.success(42).get_or_else(12)                #=> 42
    Fear.failure(ArgumentError.new).get_or_else(12) #=> 12

@!method include?(other_value)

Returns +true+ if it has an element that is equal
(as determined by +==+) to +other_value+, +false+ otherwise.
@param [any]
@return [Boolean]
@example
  Fear.success(17).include?(17)                #=> true
  Fear.success(17).include?(7)                 #=> false
  Fear.failure(ArgumentError.new).include?(17) #=> false

@!method each(&block)

Performs the given block if this is a +Success+.
@note if block raise an error, then this method may raise an exception.
@yieldparam [any] value
@yieldreturn [void]
@return [Try] itself
@example
  Fear.success(17).each do |value|
    puts value
  end #=> prints 17

  Fear.failure(ArgumentError.new).each do |value|
    puts value
  end #=> does nothing

@!method map(&block)

Maps the given block to the value from this +Success+ or
returns this if this is a +Failure+.
@yieldparam [any] value
@yieldreturn [any]
@example
  Fear.success(42).map { |v| v/2 }                 #=> Fear.success(21)
  Fear.failure(ArgumentError.new).map { |v| v/2 }  #=> Fear.failure(ArgumentError.new)

@!method flat_map(&block)

Returns the given block applied to the value from this +Success+
or returns this if this is a +Failure+.
@yieldparam [any] value
@yieldreturn [Try]
@return [Try]
@example
  Fear.success(42).flat_map { |v| Fear.success(v/2) }
    #=> Fear.success(21)
  Fear.failure(ArgumentError.new).flat_map { |v| Fear.success(v/2) }
    #=> Fear.failure(ArgumentError.new)

@!method to_option

Returns an +Some+ containing the +Success+ value or a +None+ if
this is a +Failure+.
@return [Option]
@example
  Fear.success(42).to_option                 #=> Fear.some(21)
  Fear.failure(ArgumentError.new).to_option  #=> Fear.none()

@!method any?(&predicate)

Returns +false+ if +Failure+ or returns the result of the
application of the given predicate to the +Success+ value.
@yieldparam [any] value
@yieldreturn [Boolean]
@return [Boolean]
@example
  Fear.success(12).any?( |v| v > 10)                #=> true
  Fear.success(7).any?( |v| v > 10)                 #=> false
  Fear.failure(ArgumentError.new).any?( |v| v > 10) #=> false

@!method success?

Returns +true+ if it is a +Success+, +false+ otherwise.
@return [Boolean]

@!method failure?

Returns +true+ if it is a +Failure+, +false+ otherwise.
@return [Boolean]

@!method get

Returns the value from this +Success+ or raise the exception
if this is a +Failure+.
@return [any]
@example
  Fear.success(42).get                 #=> 42
  Fear.failure(ArgumentError.new).get  #=> ArgumentError: ArgumentError

@!method or_else(&alternative)

Returns this +Try+ if it's a +Success+ or the given alternative if this is a +Failure+.
@return [Try]
@example
  Fear.success(42).or_else { Fear.success(-1) }                 #=> Fear.success(42)
  Fear.failure(ArgumentError.new).or_else { Fear.success(-1) }  #=> Fear.success(-1)
  Fear.failure(ArgumentError.new).or_else { Fear.try { 1/0 } }
    #=> Fear.failure(ZeroDivisionError.new('divided by 0'))

@!method flatten

Transforms a nested +Try+, ie, a +Success+ of +Success+,
into an un-nested +Try+, ie, a +Success+.
@return [Try]
@example
  Fear.success(42).flatten                         #=> Fear.success(42)
  Fear.success(Fear.success(42)).flatten                #=> Fear.success(42)
  Fear.success(Fear.failure(ArgumentError.new)).flatten #=> Fear.failure(ArgumentError.new)
  Fear.failure(ArgumentError.new).flatten { -1 }   #=> Fear.failure(ArgumentError.new)

@!method select(&predicate)

Converts this to a +Failure+ if the predicate is not satisfied.
@yieldparam [any] value
@yieldreturn [Boolean]
@return [Try]
@example
  Fear.success(42).select { |v| v > 40 }
    #=> Fear.success(21)
  Fear.success(42).select { |v| v < 40 }
    #=> Fear.failure(Fear::NoSuchElementError.new("Predicate does not hold for 42"))
  Fear.failure(ArgumentError.new).select { |v| v < 40 }
    #=> Fear.failure(ArgumentError.new)

@!method recover_with(&block)

Applies the given block to exception. This is like +flat_map+
for the exception.
@yieldparam [Fear::PatternMatch] matcher
@yieldreturn [Fear::Try]
@return [Fear::Try]
@example
  Fear.success(42).recover_with do |m|
    m.case(ZeroDivisionError) { Fear.success(0) }
  end #=> Fear.success(42)

  Fear.failure(ArgumentError.new).recover_with do |m|
    m.case(ZeroDivisionError) { Fear.success(0) }
    m.case(ArgumentError) { |error| Fear.success(error.class.name) }
  end #=> Fear.success('ArgumentError')

  # If the block raises error, this new error returned as an result

  Fear.failure(ArgumentError.new).recover_with do |m|
    raise
  end #=> Fear.failure(RuntimeError)

@!method recover(&block)

Applies the given block to exception. This is like +map+ for the exception.
@yieldparam [Fear::PatternMatch] matcher
@yieldreturn [any]
@return [Fear::Try]
@example #recover
  Fear.success(42).recover do |m|
    m.case(&:message)
  end #=> Fear.success(42)

  Fear.failure(ArgumentError.new).recover do |m|
    m.case(ZeroDivisionError) { 0 }
    m.case(&:message)
  end #=> Fear.success('ArgumentError')

  # If the block raises error, this new error returned as an result

  Fear.failure(ArgumentError.new).recover do |m|
    raise
  end #=> Fear.failure(RuntimeError)

@!method to_either

Returns +Left+ with exception if this is a +Failure+, otherwise
returns +Right+ with +Success+ value.
@return [Right<any>, Left<StandardError>]
@example
  Fear.success(42).to_either                #=> Fear.right(42)
  Fear.failure(ArgumentError.new).to_either #=> Fear.left(ArgumentError.new)

@!method match(&matcher)

Pattern match against this +Try+
@yield matcher [Fear::TryPatternMatch]
@example
  Fear.try { ... }.match do |m|
    m.success(Integer) do |x|
     x * 2
    end

    m.success(String) do |x|
      x.to_i * 2
    end

    m.failure(ZeroDivisionError) { 'not allowed to divide by 0' }
    m.else { 'something unexpected' }
  end

@author based on Twitter's original implementation. @see github.com/scala/scala/blob/2.11.x/src/library/scala/util/Try.scala

Public Class Methods

matcher(&matcher) click to toggle source

Build pattern matcher to be used later, despite off +Try#match+ method, id doesn't apply matcher immanently, but build it instead. Unusually in sake of efficiency it's better to statically build matcher and reuse it later.

@example

matcher =
  Try.matcher do |m|
    m.success(Integer, ->(x) { x > 2 }) { |x| x * 2 }
    m.success(String) { |x| x.to_i * 2 }
    m.failure(ActiveRecord::RecordNotFound) { :err }
    m.else { 'error '}
  end
matcher.call(try)

@yieldparam [Fear::TryPatternMatch] @return [Fear::PartialFunction]

# File lib/fear/try.rb, line 281
def matcher(&matcher)
  TryPatternMatch.new(&matcher)
end

Public Instance Methods

left_class() click to toggle source

@private

# File lib/fear/try.rb, line 254
def left_class
  Failure
end
right_class() click to toggle source

@private

# File lib/fear/try.rb, line 259
def right_class
  Success
end