-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Projects this `Fear::Either` as a `Fear::Left`. This allows performing right-biased operation of the left side of the `Fear::Either`: ```ruby Fear.left(42).left.map(&:succ) #=> Fear.left(43) Fear.right(42).left.map(&:succ) #=> Fear.left(42) ```
- Loading branch information
Showing
9 changed files
with
591 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
# frozen_string_literal: true | ||
|
||
module Fear | ||
module Either | ||
# Projects an `Either` into a `Left`. | ||
# @see Fear::Either#left | ||
# | ||
class LeftProjection | ||
extend Utils::Assertions | ||
|
||
# @!attribute either | ||
# @return [Fear::Either] | ||
attr_reader :either | ||
protected :either | ||
|
||
# @param either [Fear::Either] | ||
def initialize(either) | ||
@either = either | ||
end | ||
|
||
# Returns +true+ if +Fear::Left+ has an element that is equal | ||
# (as determined by +==+) to +other_value+, +false+ otherwise. | ||
# @param [Object] | ||
# @return [Boolean] | ||
# @example | ||
# Fear.left(17).left.include?(17) #=> true | ||
# Fear.left(17).left.include?(7) #=> false | ||
# Fear.right('undefined').left.include?(17) #=> false | ||
# | ||
def include?(other_value) | ||
case either | ||
in Fear::Left(x) | ||
x == other_value | ||
in Fear::Right | ||
false | ||
end | ||
end | ||
|
||
# Returns the value from this +Fear::Left+ or evaluates the given | ||
# default argument if this is a +Fear::Right+. | ||
# | ||
# @overload get_or_else(&default) | ||
# @yieldreturn [Object] | ||
# @return [Object] | ||
# @example | ||
# Fear.right(42).left.get_or_else { 24/2 } #=> 12 | ||
# Fear.left('undefined').left.get_or_else { 24/2 } #=> 'undefined' | ||
# | ||
# @overload get_or_else(default) | ||
# @return [Object] | ||
# @example | ||
# Fear.right(42).left.get_or_else(12) #=> 12 | ||
# Fear.left('undefined').left.get_or_else(12) #=> 'undefined' | ||
def get_or_else(*args) | ||
case either | ||
in Fear::Left(value) | ||
value | ||
in Fear::Right | ||
args.fetch(0) { yield } | ||
end | ||
end | ||
assert_arg_or_block :get_or_else | ||
|
||
# Performs the given block if this is a +Fear::Left+. | ||
# | ||
# @yieldparam [Object] value | ||
# @yieldreturn [void] | ||
# @return [Fear::Either] itself | ||
# @example | ||
# Fear.right(17).left.each do |value| | ||
# puts value | ||
# end #=> does nothing | ||
# | ||
# Fear.left('undefined').left.each do |value| | ||
# puts value | ||
# end #=> prints "nothing" | ||
def each | ||
case either | ||
in Fear::Left(value) | ||
yield(value) | ||
either | ||
in Fear::Right | ||
either | ||
end | ||
end | ||
|
||
# Maps the block argument through +Fear::Left+. | ||
# | ||
# @yieldparam [Object] value | ||
# @yieldreturn [Fear::Either] | ||
# @example | ||
# Fear.left(42).left.map { _1/2 } #=> Fear.left(24) | ||
# Fear.right(42).left.map { _1/2 } #=> Fear.right(42) | ||
# | ||
def map | ||
case either | ||
in Fear::Left(value) | ||
Fear.left(yield(value)) | ||
in Fear::Right | ||
either | ||
end | ||
end | ||
|
||
# Returns the given block applied to the value from this +Fear::Left+ | ||
# or returns this if this is a +Fear::Right+. | ||
# | ||
# @yieldparam [Object] value | ||
# @yieldreturn [Fear::Either] | ||
# @return [Fear::Either] | ||
# | ||
# @example | ||
# Fear.left(12).left.flat_map { Fear.left(_1 * 2) } #=> Fear.left(24) | ||
# Fear.left(12).left.flat_map { Fear.right(_1 * 2) } #=> Fear.right(24) | ||
# Fear.right(12).left.flat_map { Fear.left(_1 * 2) } #=> Fear.right(12) | ||
# | ||
def flat_map | ||
case either | ||
in Fear::Left(value) | ||
yield(value) | ||
in Fear::Right | ||
either | ||
end | ||
end | ||
assert_return Fear::Either, :flat_map | ||
|
||
# Returns an +Fear::Some+ containing the +Fear::Left+ value or a +Fear::None+ if | ||
# this is a +Fear::Right+. | ||
# @return [Fear::Option] | ||
# @example | ||
# Fear.left(42).left.to_option #=> Fear.some(42) | ||
# Fear.right(42).left.to_option #=> Fear.none | ||
# | ||
def to_option | ||
case either | ||
in Fear::Left(value) | ||
Fear.some(value) | ||
in Fear::Right | ||
Fear.none | ||
end | ||
end | ||
|
||
# Returns an array containing the +Fear::Left+ value or an empty array if | ||
# this is a +Fear::Right+. | ||
# | ||
# @return [Array] | ||
# @example | ||
# Fear.left(42).left.to_a #=> [42] | ||
# Fear.right(42).left.to_a #=> [] | ||
# | ||
def to_a | ||
case either | ||
in Fear::Left(value) | ||
[value] | ||
in Fear::Right | ||
[] | ||
end | ||
end | ||
|
||
# Returns +false+ if +Fear::Right+ or returns the result of the | ||
# application of the given predicate to the +Fear::Light+ value. | ||
# | ||
# @yieldparam [Object] value | ||
# @yieldreturn [Boolean] | ||
# @return [Boolean] | ||
# @example | ||
# Fear.left(12).left.any? { |v| v > 10 } #=> true | ||
# Fear.left(7).left.any? { |v| v > 10 } #=> false | ||
# Fear.right(12).left.any? { |v| v > 10 } #=> false | ||
# | ||
def any?(&predicate) | ||
case either | ||
in Fear::Left(value) | ||
predicate.(value) | ||
in Fear::Right | ||
false | ||
end | ||
end | ||
|
||
# Returns +Fear::Right+ of value if the given predicate | ||
# does not hold for the left value, otherwise, returns +Fear::Left+. | ||
# | ||
# @yieldparam value [Object] | ||
# @yieldreturn [Boolean] | ||
# @return [Fear::Either] | ||
# @example | ||
# Fear.left(12).left.select(&:even?) #=> Fear.left(12) | ||
# Fear.left(7).left.select(&:even?) #=> Fear.right(7) | ||
# Fear.right(12).left.select(&:even?) #=> Fear.right(12) | ||
# Fear.right(7).left.select(&:even?) #=> Fear.right(7) | ||
# | ||
def select(&predicate) | ||
case either | ||
in Fear::Right | ||
either | ||
in Fear::Left(value) if predicate.(value) | ||
either | ||
in Fear::Left | ||
either.swap | ||
end | ||
end | ||
|
||
# Returns +Fear::None+ if this is a +Fear::Right+ or if the given predicate | ||
# does not hold for the left value, otherwise, returns a +Fear::Left+. | ||
# | ||
# @yieldparam value [Object] | ||
# @yieldreturn [Boolean] | ||
# @return [Fear::Option<Fear::Either>] | ||
# @example | ||
# Fear.left(12).left.find(&:even?) #=> #<Fear::Some value=#<Fear::Left value=12>> | ||
# Fear.left(7).left.find(&:even?) #=> #<Fear::None> | ||
# Fear.right(12).left.find(&:even) #=> #<Fear::None> | ||
# | ||
def find(&predicate) | ||
case either | ||
in Fear::Left(value) if predicate.(value) | ||
Fear.some(either) | ||
in Fear::Either | ||
Fear.none | ||
end | ||
end | ||
alias detect find | ||
|
||
# @param other [Object] | ||
# @return [Boolean] | ||
def ==(other) | ||
other.is_a?(self.class) && other.either == either | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
# frozen_string_literal: true | ||
|
||
require "fear/utils/assertions" | ||
|
||
module Fear | ||
# @private | ||
module Utils | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# frozen_string_literal: true | ||
|
||
module Fear | ||
module Utils | ||
# @api private | ||
module Assertions | ||
def assert_return(expected_type, method_name) | ||
alias_method "#{method_name}_without_return_type_check", method_name | ||
|
||
define_method(method_name) do |*args, &block| | ||
result = __send__("#{method_name}_without_return_type_check", *args, &block) | ||
|
||
unless expected_type === result | ||
raise TypeError, "#{self.class}##{method_name} expected to return #{expected_type.inspect}, " \ | ||
"but returned #{result.inspect}" | ||
end | ||
|
||
result | ||
end | ||
end | ||
|
||
def assert_arg_or_block(method_name) | ||
alias_method "#{method_name}_without_arg_or_block_check", method_name | ||
|
||
define_method(method_name) do |*args, &block| | ||
unless !block.nil? ^ !args.empty? | ||
raise ArgumentError, "##{method_name} accepts either one argument or block" | ||
end | ||
|
||
__send__("#{method_name}_without_arg_or_block_check", *args, &block) | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.