lib/fear/for.rb in fear-0.10.0 vs lib/fear/for.rb in fear-0.11.0
- old
+ new
@@ -2,128 +2,123 @@
# This class provides syntactic sugar for composition of
# multiple monadic operations. It supports two such
# operations - +flat_map+ and +map+. Any class providing them
# is supported by +For+.
#
- # For(a: Some(2), b: Some(3)) { a * b } #=> Some(6)
+ # For(Some(2), Some(3)) do |a, b|
+ # a * b
+ # end #=> Some(6)
#
# If one of operands is None, the result is None
#
- # For(a: Some(2), b: None()) { a * b } #=> None()
- # For(a: None(), b: Some(2)) { a * b } #=> None()
+ # For(Some(2), None()) { |a, b| a * b } #=> None()
+ # For(None(), Some(2)) { |a, b| a * b } #=> None()
#
# Lets look at first example:
#
- # For(a: Some(2), b: Some(3)) { a * b }
+ # For(Some(2), Some(3)) { |a, b| a * b }
#
- # would be translated to:
+ # it is translated to:
#
# Some(2).flat_map do |a|
# Some(3).map do |b|
# a * b
# end
# end
#
# It works with arrays as well
#
- # For(a: [1, 2], b: [2, 3], c: [3, 4]) { a * b * c }
+ # For([1, 2], [2, 3], [3, 4]) { |a, b, c| a * b * c }
# #=> [6, 8, 9, 12, 12, 16, 18, 24]
#
- # would be translated to:
+ # it is translated to:
#
# [1, 2].flat_map do |a|
# [2, 3].flat_map do |b|
# [3, 4].map do |c|
# a * b * c
# end
# end
# end
#
- # If you pass lambda as a variable value, it would be evaluated
+ # If you pass lambda instead of monad, it would be evaluated
# only on demand.
#
- # For(a: -> { None() }, b: -> { fail 'kaboom' } ) { a * b }
- # #=> None()
+ # For(proc { None() }, proc { raise 'kaboom' } ) do |a, b|
+ # a * b
+ # end #=> None()
#
# It does not fail since `b` is not evaluated.
- # You can refer to previously defined variables from within lambdas.
+ # You can refer to previously defined monads from within lambdas.
#
# maybe_user = find_user('Paul') #=> <#Option value=<#User ...>>
#
- # For(user: maybe_user, birthday: -> { user.birthday }) do
+ # For(maybe_user, ->(user) { user.birthday }) do |user, birthday|
# "#{user.name} was born on #{birthday}"
# end #=> Some('Paul was born on 1987-06-17')
#
- class For
- require_relative 'for/evaluation_context'
+ module For
+ module_function # rubocop: disable Style/AccessModifierDeclarations
- # @param variables [Hash{Symbol => any}]
+ # @param monads [<Fear::Option, Fear::Either, Fear::Try, Proc>]
#
- def initialize(outer_context, **variables)
- @variables = variables
- @evaluation_context = EvaluationContext.new(outer_context)
+ def call(monads, inner_values = [], &block)
+ head, *tail = *monads
+
+ if tail.length.zero?
+ map(head, inner_values, &block)
+ else
+ flat_map(head, tail, inner_values, &block)
+ end
end
- def call(&block)
- variable_name_and_monad, *tail = *variables
- execute(*variable_name_and_monad, tail, &block)
+ private def map(head, inner_values)
+ resolve(head, inner_values).map do |x|
+ yield(*inner_values, x)
+ end
end
- private
-
- attr_reader :variables
- attr_reader :evaluation_context
-
- def execute(variable_name, monad, monads, &block) # rubocop:disable Metrics/MethodLength
- if monads.empty?
- resolve(monad).map do |value|
- evaluation_context.__assign__(variable_name, value)
- evaluation_context.instance_exec(&block)
- end
- else
- resolve(monad).flat_map do |value|
- evaluation_context.__assign__(variable_name, value)
- variable_name_and_monad, *tail = *monads
- execute(*variable_name_and_monad, tail, &block)
- end
+ private def flat_map(head, tail, inner_values, &block)
+ resolve(head, inner_values).flat_map do |x|
+ call(tail, inner_values + [x], &block)
end
end
- def resolve(monad_or_proc)
+ private def resolve(monad_or_proc, inner_values)
if monad_or_proc.respond_to?(:call)
- evaluation_context.instance_exec(&monad_or_proc)
+ monad_or_proc.call(*inner_values)
else
monad_or_proc
end
end
# Include this mixin to access convenient factory method for +For+.
# @example
# include Fear::For::Mixin
#
- # For(a: Some(2), b: Some(3)) { a * b } #=> Some(6)
- # For(a: Some(2), b: None()) { a * b } #=> None()
+ # For(Some(2), Some(3)) { |a, b| a * b } #=> Some(6)
+ # For(Some(2), None()) { |a, b| a * b } #=> None()
#
- # For(a: -> { Some(2) }, b: -> { Some(3) }) do
+ # For(proc { Some(2) }, proc { Some(3) }) do |a, b|
# a * b
# end #=> Some(6)
#
- # For(a: -> { None() }, b: -> { fail }) do
+ # For(proc { None() }, proc { raise }) do |a, b|
# a * b
# end #=> None()
#
- # For(a: Right(2), b: Right(3)) { a * b } #=> Right(6)
- # For(a: Right(2), b: Left(3)) { a * b } #=> Left(3)
+ # For(Right(2), Right(3)) { |a, b| a * b } #=> Right(6)
+ # For(Right(2), Left(3)) { |a, b| a * b } #=> Left(3)
#
- # For(a: Success(2), b: Success(3)) { a * b } #=> Success(3)
- # For(a: Success(2), b: Failure(...)) { a * b } #=> Failure(...)
+ # For(Success(2), Success(3)) { |a| a * b } #=> Success(3)
+ # For(Success(2), Failure(...)) { |a, b| a * b } #=> Failure(...)
#
module Mixin
- # @param locals [Hash{Symbol => {#map, #flat_map}}]
+ # @param monads [Hash{Symbol => {#map, #flat_map}}]
# @return [{#map, #flat_map}]
#
- def For(**locals, &block)
- For.new(self, **locals).call(&block)
+ def For(*monads, &block)
+ For.call(monads, &block)
end
end
end
end