class Porolog::Variable
A Porolog::Variable
is used to hold instantiations during the process of satisfying a goal. It implements a variable of a goal. It allows instantiations to be made and removed as the goal is attempted to be satisfied.
@author Luis Esteban
@!attribute name
@return [Symbol] The name of the variable.
@!attribute goal
@return [Porolog::Goal] The Goal for which this Variable's instantiations are bound.
@!attribute instantiations
@return [Array<Porolog::Instantiation>] The Instantiations of this Variable.
@!attribute values
@return [Array<Porolog::Value>] the Values of the Variable when used as an anonymous Variable.
Attributes
Public Class Methods
Initializes the Variable
and attaches it to the Goal
. @param name [Object] the name used to refer to the Variable
. @param goal [Porolog::Goal] the Goal
the Variable
is to be attached to.
# File lib/porolog/variable.rb, line 44 def initialize(name, goal) raise GoalError, "Not a Goal: #{goal.inspect}" unless goal.is_a?(Goal) @goal = goal name = name.to_sym if name.is_a?(String) case name when Symbol @name = name @values = [] when Variable @name = name.name @values = [] when Value @name = name.value @values = [name] else @name = name.to_s @values = [Value.new(name, goal)] end @instantiations = [] @goal.variable(self) end
Public Instance Methods
Indexes the value of the Variable
. @param index [Object] the index into the value. @return [Object, nil] the value at the index in the value of the Variable
.
# File lib/porolog/variable.rb, line 285 def [](index) value = self.value value = value.value if value.is_a?(Value) case value when Array case index when Integer value[index] when Symbol case index when :head value[0] when :tail value[1..-1] else nil end else nil end else nil end end
@return [String] pretty representation.
# File lib/porolog/variable.rb, line 80 def inspect "#{@goal.myid}.#{@name.inspect}" end
@param visited [Array] used to prevent infinite recursion. @param depth [Integer] the current level of indentation. @param index [Object, nil] the index into this Variable
that is instantiated. @param self_index [Object, nil] the index where this Variable
is instantiated. @return [String] the inspect of the Variable
showing instantiations using indentation.
# File lib/porolog/variable.rb, line 89 def inspect_with_instantiations(visited = [], depth = 0, index = nil, self_index = nil) return if visited.include?(self) index_str = index && "[#{index.inspect}]" || '' prefix = self_index && "[#{self_index.inspect}]" || '' name = "#{' ' * depth}#{prefix}#{@goal.myid}.#{@name.inspect}#{index_str}" name = "#{name} = #{@values.map(&:inspect).join(',')}#{index_str}" if @values && !@values.empty? && @instantiations.empty? others = @instantiations.map{|instantiation| [ instantiation.variable1.inspect_with_instantiations(visited + [self], depth + 1, instantiation.index1, instantiation.index2), instantiation.variable2.inspect_with_instantiations(visited + [self], depth + 1, instantiation.index2, instantiation.index1), ].compact }.reject(&:empty?) [ name, *others, ].join("\n") end
Instantiates Variable
to another experssion. @param other [Object] the other value (or object) being instantiated. @param index_into_other [] the index into the other value. @param index_into_self [] the index into this Variable
. @return [Porolog::Instantiation,nil] the instantiation made. @example
# To instantiate the third element of x to the second element of y, # x = [a,b,c,d,e] # | # y = [p,q,r,s] x = goal.variable(:x) y = goal.variable(:y) x.instantiate(y, 1, 2)
@example
# To instantiate the tail of x to y, x.instantiate(y, nil, :tail)
# File lib/porolog/variable.rb, line 225 def instantiate(other, index_into_other = nil, index_into_self = nil) raise SelfReferentialError, "Cannot instantiate self-referential definition for #{(self.variables & other.variables).inspect}" unless (self.variables & other.variables).empty? # -- Check Instantiation is Unifiable -- unless self.value.is_a?(Variable) || other.value.is_a?(Variable) # -- Determine Other Goal -- other_goal = nil other_goal = other.goal if other.respond_to?(:goal) other_goal ||= self.goal # -- Check Unification -- unless Porolog::unify(self, other, self.goal, other_goal) self.goal.log << "Cannot unify: #{self.inspect} and #{other.inspect}" return nil end end # -- Create Instantiation -- instantiation = Instantiation.new(self, index_into_self, other, index_into_other) # -- Create Reverse Assymetric Instantiations -- if other.value.is_a?(Array) && other.value.last.is_a?(Tail) array = other.value if array.length == 2 if array.first.is_a?(Variable) Instantiation.new(array.first, nil, self, :head) end if array.last.is_a?(Tail) && array.last.value.is_a?(Variable) Instantiation.new(array.last.value, nil, self, :tail) end end end # -- Return -- instantiation end
Removes this Variable
by removing all of its insantiations. @return [Boolean] success.
# File lib/porolog/variable.rb, line 264 def remove @instantiations.dup.each(&:remove) @instantiations[0..-1] = [] true end
@return [Symbol] the type of the Variable
, which should be :variable.
# File lib/porolog/variable.rb, line 75 def type :variable end
Removes instantiations from another goal. @param other_goal [Porolog::Goal] the Goal
of which instantiations are to be removed. @return [Boolean] success.
# File lib/porolog/variable.rb, line 273 def uninstantiate(other_goal) @instantiations.delete_if do |instantiation| instantiation.remove if instantiation.other_goal_to(self) == other_goal end @values.delete_if{|value| value.goal == other_goal } true end
@return [Object,self] returns the current value of the Variable
based on its current instantiations.
If there are no concrete instantiations, it returns itself, indicating no value.
@param visited [Array] prevents infinite recursion.
# File lib/porolog/variable.rb, line 114 def value(visited = []) return nil if visited.include?(self) visited = visited + [self] # -- Collect values -- values = [*@values] @instantiations.each do |instantiation| values += instantiation.values_for(self, visited) end values.uniq! # -- Filter trivial values -- values = [[nil]] if values == [[UNKNOWN_TAIL], [nil]] || values == [[nil], [UNKNOWN_TAIL]] values = values.reject{|value| value == UNKNOWN_TAIL } values_values = values.map{|value| value.value(visited) }.reject{|value| value == UNKNOWN_ARRAY } # -- Condense Values -- result = if values_values.size > 1 # -- Potentially Multiple Values Found -- unifications = [] if values_values.all?{|value| value.is_a?(Array) } # -- All Values Are Arrays -- values = values.reject{|value| value == UNKNOWN_ARRAY } if values.size > 2 && values.include?(UNKNOWN_ARRAY) values_goals = values.map{|value| value.respond_to?(:goal) && value.goal || value.variables.map(&:goal).first || values.variables.map(&:goal).first } if values.size > 2 merged, unifications = Porolog::unify_many_arrays(values, values_goals, visited) elsif values.size == 2 no_variables = values.map(&:variables).flatten.empty? if no_variables left_value = values[0].value.value right_value = values[1].value.value if left_value.last == UNKNOWN_TAIL && right_value.first == UNKNOWN_TAIL return [*left_value[0...-1], *right_value[1..-1]] elsif right_value.last == UNKNOWN_TAIL && left_value.first == UNKNOWN_TAIL return [*right_value[0...-1], *left_value[1..-1]] elsif left_value != right_value return nil end end merged, unifications = Porolog::unify_arrays(*values, *values_goals, visited) else # :nocov: NOT REACHED merged, unifications = values.first, [] # :nocov: end merged.value(visited).to_a else # -- Not All Values Are Arrays -- values.each_cons(2){|left,right| unification = Porolog::unify(left, right, @goal, @goal, visited) if unification && unifications unifications += unification else unifications = nil end } if unifications values.min_by{|value| case value when Porolog::Variable, Symbol then 2 when Porolog::UNKNOWN_TAIL, Porolog::UNKNOWN_ARRAY then 9 else 0 end } || self else raise MultipleValuesError, "Multiple values detected for #{inspect}: #{values.inspect}" end end else # -- One (or None) Value Found -- values.min_by{|value| case value when Variable, Symbol then 2 when UNKNOWN_TAIL, UNKNOWN_ARRAY then 9 else 0 end } || self end # -- Splat Tail -- if result.is_a?(Array) && result.size == 1 && result.first.is_a?(Tail) result = result.first.value end result end
@return [Array<Porolog::Variable>] the Variables in itself, which is just itself.
# File lib/porolog/variable.rb, line 312 def variables [self] end