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
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
@private
# File lib/fear/try.rb, line 254 def left_class Failure end
@private
# File lib/fear/try.rb, line 259 def right_class Success end