module Dry module Logic def self.Result(input, value, rule) case value when Result value.class.new(value.input, value.success?, rule) when Array Result::Set.new(input, value, rule) else Result::Value.new(input, value, rule) end end class Result include Dry::Equalizer(:success?, :input, :rule) attr_reader :input, :value, :rule, :name class Result::Set < Result def success? value.all?(&:success?) end def to_ary indices = value.map { |v| v.failure? ? value.index(v) : nil }.compact [:input, [name, input, value.values_at(*indices).map(&:to_ary)]] end end class Result::Value < Result def to_ary [:input, [name, input, [rule.to_ary]]] end alias_method :to_a, :to_ary end class Result::LazyValue < Result def to_ary [:input, [rule.name, input, [rule.to_ary]]] end alias_method :to_a, :to_ary def input success? ? rule.evaluate_input(@input) : @input end end class Result::Wrapped < Result::Value def to_ary [:input, [rule.name, rule.evaluate_input(input), [rule.to_ary]]] end alias_method :to_a, :to_ary def wrapped? true end end class Result::Verified < Result attr_reader :predicate_id def initialize(result, predicate_id) @input = result.input @value = result.value @rule = result.rule @name = result.name @predicate_id = predicate_id end def call(*) Logic.Result(input, success?, rule) end def to_ary [:input, [name, input, [rule.to_ary]]] end alias_method :to_a, :to_ary def success? rule.predicate_id == predicate_id end end def initialize(input, value, rule) @input = input @value = value @rule = rule @name = rule.name end def call(*) self end def curry(predicate_id = nil) if predicate_id Result::Verified.new(self, predicate_id) else self end end def negated self.class.new(input, !value, rule) end def then(other) if success? other.(input) else Logic.Result(input, true, rule) end end def and(other) if success? other.(input) else self end end def or(other) if success? self else other.(input) end end def xor(other) other_result = other.(input) value = success? ^ other_result.success? if other_result.wrapped? Result::Wrapped.new(input, value, rule) else Logic.Result(other_result.input, value, rule) end end def success? @value end def failure? ! success? end def wrapped? false end end end end