Class: Porolog::Arguments

Inherits:
Object show all
Defined in:
lib/porolog/arguments.rb

Overview

A Porolog::Arguments specifies arguments of a Predicate. A predicate is not like a subroutine, although there are many similarities. An Arguments represents an instance of a predicate with a specific set of arguments. This forms the basis for implementing a goal to solve the predicate with those specific arguments.

Author:

  • Luis Esteban

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(predicate, arguments, &block) ⇒ Arguments

Creates a new Arguments for a Predicate



37
38
39
40
41
42
# File 'lib/porolog/arguments.rb', line 37

def initialize(predicate, arguments, &block)
  @predicate = predicate
  @arguments = arguments
  @block     = block
  @@arguments << self
end

Instance Attribute Details

#argumentsObject (readonly)

The actual arguments.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/porolog/arguments.rb', line 21

class Arguments
  
  attr_reader :predicate, :arguments, :block
  
  # Unregisters all Arguments
  # @return [true]
  def self.reset
    @@arguments = []
    true
  end
  
  reset
  
  # Creates a new Arguments for a Predicate
  # @param predicate [Porolog::Predicate] the Predicate for which these are the arguments
  # @param arguments [Array<Object>] the actual arguments
  def initialize(predicate, arguments, &block)
    @predicate = predicate
    @arguments = arguments
    @block     = block
    @@arguments << self
  end
  
  # Convenience method for testing/debugging
  # @return [Array<Porolog::Arguments>] all registered Arguments
  def self.arguments
    @@arguments
  end
  
  # Convenience method for testing/debugging
  # @return [String] pretty identification
  def myid
    "Arguments#{(@@arguments.index(self) || -1000) + 1}"
  end
  
  # @return [String] pretty representation
  def inspect
    block_inspect = block.nil? ? '' : "{#{block.inspect}}"
    "#{@predicate&.name}(#{@arguments&.map(&:inspect).join(',')})#{block_inspect}"
  end
  
  # Creates a fact rule that states that these arguments satisfy the Predicate.
  # @return [Porolog::Arguments] the Arguments
  def fact!
    @predicate << Rule.new(self, true)
    self
  end
  
  # Creates a fact rule that states that these arguments do not satisfy the Predicate.
  # @return [Porolog::Arguments] the Arguments
  def fallacy!
    @predicate << Rule.new(self, false)
    self
  end
  
  # Creates a fact rule that states that these arguments satisfy the Predicate and terminates solving the predicate.
  # @return [Porolog::Arguments] the Arguments
  def cut_fact!
    @predicate << Rule.new(self, [:CUT, true])
    self
  end
  
  # Creates a fact rule that states that these arguments do not satisfy the Predicate and terminates solving the predicate.
  # @return [Porolog::Arguments] the Arguments
  def cut_fallacy!
    @predicate << Rule.new(self, [:CUT, false])
    self
  end
  
  # Adds a Rule to the Predicate for these Arguments
  # @param definition [Array<Object>, Object] the Rule definition for these Arguments
  # @return [Porolog::Arguments] the Arguments
  def <<(*definition)
    @predicate << Rule.new(self, *definition)
    self
  end
  
  # Adds an evaluation Rule to the Predicate
  # @param block the block to evaluate
  # @return [Porolog::Arguments] the Arguments
  def evaluates(&block)
    @predicate << Rule.new(self, block)
    self
  end
  
  # @return [Array<Symbol>] the variables contained in the arguments
  def variables
    @arguments.map(&:variables).flatten.uniq
  end
  
  # Creates a Goal for solving this Arguments for the Predicate
  # @param calling_goal [Porolog::Goal] the parent Goal (if this is a subgoal)
  # @return [Porolog::Goal] the Goal to solve
  def goal(calling_goal = nil)
    Goal.new(self, calling_goal)
  end
  
  # Returns memoized solutions
  # @param max_solutions [Integer] the maximum number of solutions to find (nil means find all)
  # @return [Array<Hash{Symbol => Object}>] the solutions found (memoized)
  def solutions(max_solutions = nil)
    @solutions ||= solve(max_solutions)
    max_solutions && @solutions[0...max_solutions] || @solutions
  end
  
  # Solves the Arguments
  # @param max_solutions [Integer] the maximum number of solutions to find (nil means find all)
  # @return [Array<Hash{Symbol => Object}>] the solutions found
  def solve(max_solutions = nil)
    @solutions = goal.solve(max_solutions)
  end
  
  # Extracts solution values.
  # @param variables [Symbol, Array<Symbol>] variable or variables
  # @param max_solutions [Integer] the maximum number of solutions to find (nil means find all)
  # @return [Array<Object>] all the values for the variables given
  # @example
  #   predicate :treasure_at
  #   treasure_at(:X,:Y) << [...]
  #   arguments = treasure_at(:X,:Y)
  #   xs = arguments.solve_for(:X)
  #   ys = arguments.solve_for(:Y)
  #   coords = xs.zip(ys)
  def solve_for(*variables, max_solutions: nil)
    variables = [*variables]
    solutions(max_solutions).map{|solution|
      values = variables.map{|variable| solution[variable] }
      if values.size == 1
        values.first
      else
        values
      end
    }
  end
  
  # @return [Boolean] whether any solutions were found
  def valid?
    !solutions(1).empty?
  end
  
  # Duplicates the Arguments in the context of the given goal
  # @param goal [Porolog::Goal] the destination goal
  # @return [Porolog::Arguments] the duplicated Arguments
  def dup(goal)
    self.class.new @predicate, goal.variablise(arguments), &@block
  end
  
  # @param other [Porolog::Arguments] arguments for comparison
  # @return [Boolean] whether the Arguments match
  def ==(other)
    @predicate == other.predicate &&
    @arguments == other.arguments
  end
  
end

#blockObject (readonly)

Returns the value of attribute block.



23
24
25
# File 'lib/porolog/arguments.rb', line 23

def block
  @block
end

#predicateObject (readonly)

The Predicate for which these are the arguments.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/porolog/arguments.rb', line 21

class Arguments
  
  attr_reader :predicate, :arguments, :block
  
  # Unregisters all Arguments
  # @return [true]
  def self.reset
    @@arguments = []
    true
  end
  
  reset
  
  # Creates a new Arguments for a Predicate
  # @param predicate [Porolog::Predicate] the Predicate for which these are the arguments
  # @param arguments [Array<Object>] the actual arguments
  def initialize(predicate, arguments, &block)
    @predicate = predicate
    @arguments = arguments
    @block     = block
    @@arguments << self
  end
  
  # Convenience method for testing/debugging
  # @return [Array<Porolog::Arguments>] all registered Arguments
  def self.arguments
    @@arguments
  end
  
  # Convenience method for testing/debugging
  # @return [String] pretty identification
  def myid
    "Arguments#{(@@arguments.index(self) || -1000) + 1}"
  end
  
  # @return [String] pretty representation
  def inspect
    block_inspect = block.nil? ? '' : "{#{block.inspect}}"
    "#{@predicate&.name}(#{@arguments&.map(&:inspect).join(',')})#{block_inspect}"
  end
  
  # Creates a fact rule that states that these arguments satisfy the Predicate.
  # @return [Porolog::Arguments] the Arguments
  def fact!
    @predicate << Rule.new(self, true)
    self
  end
  
  # Creates a fact rule that states that these arguments do not satisfy the Predicate.
  # @return [Porolog::Arguments] the Arguments
  def fallacy!
    @predicate << Rule.new(self, false)
    self
  end
  
  # Creates a fact rule that states that these arguments satisfy the Predicate and terminates solving the predicate.
  # @return [Porolog::Arguments] the Arguments
  def cut_fact!
    @predicate << Rule.new(self, [:CUT, true])
    self
  end
  
  # Creates a fact rule that states that these arguments do not satisfy the Predicate and terminates solving the predicate.
  # @return [Porolog::Arguments] the Arguments
  def cut_fallacy!
    @predicate << Rule.new(self, [:CUT, false])
    self
  end
  
  # Adds a Rule to the Predicate for these Arguments
  # @param definition [Array<Object>, Object] the Rule definition for these Arguments
  # @return [Porolog::Arguments] the Arguments
  def <<(*definition)
    @predicate << Rule.new(self, *definition)
    self
  end
  
  # Adds an evaluation Rule to the Predicate
  # @param block the block to evaluate
  # @return [Porolog::Arguments] the Arguments
  def evaluates(&block)
    @predicate << Rule.new(self, block)
    self
  end
  
  # @return [Array<Symbol>] the variables contained in the arguments
  def variables
    @arguments.map(&:variables).flatten.uniq
  end
  
  # Creates a Goal for solving this Arguments for the Predicate
  # @param calling_goal [Porolog::Goal] the parent Goal (if this is a subgoal)
  # @return [Porolog::Goal] the Goal to solve
  def goal(calling_goal = nil)
    Goal.new(self, calling_goal)
  end
  
  # Returns memoized solutions
  # @param max_solutions [Integer] the maximum number of solutions to find (nil means find all)
  # @return [Array<Hash{Symbol => Object}>] the solutions found (memoized)
  def solutions(max_solutions = nil)
    @solutions ||= solve(max_solutions)
    max_solutions && @solutions[0...max_solutions] || @solutions
  end
  
  # Solves the Arguments
  # @param max_solutions [Integer] the maximum number of solutions to find (nil means find all)
  # @return [Array<Hash{Symbol => Object}>] the solutions found
  def solve(max_solutions = nil)
    @solutions = goal.solve(max_solutions)
  end
  
  # Extracts solution values.
  # @param variables [Symbol, Array<Symbol>] variable or variables
  # @param max_solutions [Integer] the maximum number of solutions to find (nil means find all)
  # @return [Array<Object>] all the values for the variables given
  # @example
  #   predicate :treasure_at
  #   treasure_at(:X,:Y) << [...]
  #   arguments = treasure_at(:X,:Y)
  #   xs = arguments.solve_for(:X)
  #   ys = arguments.solve_for(:Y)
  #   coords = xs.zip(ys)
  def solve_for(*variables, max_solutions: nil)
    variables = [*variables]
    solutions(max_solutions).map{|solution|
      values = variables.map{|variable| solution[variable] }
      if values.size == 1
        values.first
      else
        values
      end
    }
  end
  
  # @return [Boolean] whether any solutions were found
  def valid?
    !solutions(1).empty?
  end
  
  # Duplicates the Arguments in the context of the given goal
  # @param goal [Porolog::Goal] the destination goal
  # @return [Porolog::Arguments] the duplicated Arguments
  def dup(goal)
    self.class.new @predicate, goal.variablise(arguments), &@block
  end
  
  # @param other [Porolog::Arguments] arguments for comparison
  # @return [Boolean] whether the Arguments match
  def ==(other)
    @predicate == other.predicate &&
    @arguments == other.arguments
  end
  
end

Class Method Details

.argumentsArray<Porolog::Arguments>

Convenience method for testing/debugging



46
47
48
# File 'lib/porolog/arguments.rb', line 46

def self.arguments
  @@arguments
end

.resettrue

Unregisters all Arguments



27
28
29
30
# File 'lib/porolog/arguments.rb', line 27

def self.reset
  @@arguments = []
  true
end

Instance Method Details

#<<(*definition) ⇒ Porolog::Arguments

Adds a Rule to the Predicate for these Arguments



93
94
95
96
# File 'lib/porolog/arguments.rb', line 93

def <<(*definition)
  @predicate << Rule.new(self, *definition)
  self
end

#==(other) ⇒ Boolean



170
171
172
173
# File 'lib/porolog/arguments.rb', line 170

def ==(other)
  @predicate == other.predicate &&
  @arguments == other.arguments
end

#cut_fact!Porolog::Arguments

Creates a fact rule that states that these arguments satisfy the Predicate and terminates solving the predicate.



78
79
80
81
# File 'lib/porolog/arguments.rb', line 78

def cut_fact!
  @predicate << Rule.new(self, [:CUT, true])
  self
end

#cut_fallacy!Porolog::Arguments

Creates a fact rule that states that these arguments do not satisfy the Predicate and terminates solving the predicate.



85
86
87
88
# File 'lib/porolog/arguments.rb', line 85

def cut_fallacy!
  @predicate << Rule.new(self, [:CUT, false])
  self
end

#dup(goal) ⇒ Porolog::Arguments

Duplicates the Arguments in the context of the given goal



164
165
166
# File 'lib/porolog/arguments.rb', line 164

def dup(goal)
  self.class.new @predicate, goal.variablise(arguments), &@block
end

#evaluates(&block) ⇒ Porolog::Arguments

Adds an evaluation Rule to the Predicate



101
102
103
104
# File 'lib/porolog/arguments.rb', line 101

def evaluates(&block)
  @predicate << Rule.new(self, block)
  self
end

#fact!Porolog::Arguments

Creates a fact rule that states that these arguments satisfy the Predicate.



64
65
66
67
# File 'lib/porolog/arguments.rb', line 64

def fact!
  @predicate << Rule.new(self, true)
  self
end

#fallacy!Porolog::Arguments

Creates a fact rule that states that these arguments do not satisfy the Predicate.



71
72
73
74
# File 'lib/porolog/arguments.rb', line 71

def fallacy!
  @predicate << Rule.new(self, false)
  self
end

#goal(calling_goal = nil) ⇒ Porolog::Goal

Creates a Goal for solving this Arguments for the Predicate



114
115
116
# File 'lib/porolog/arguments.rb', line 114

def goal(calling_goal = nil)
  Goal.new(self, calling_goal)
end

#inspectString



57
58
59
60
# File 'lib/porolog/arguments.rb', line 57

def inspect
  block_inspect = block.nil? ? '' : "{#{block.inspect}}"
  "#{@predicate&.name}(#{@arguments&.map(&:inspect).join(',')})#{block_inspect}"
end

#myidString

Convenience method for testing/debugging



52
53
54
# File 'lib/porolog/arguments.rb', line 52

def myid
  "Arguments#{(@@arguments.index(self) || -1000) + 1}"
end

#solutions(max_solutions = nil) ⇒ Array<Hash{Symbol => Object}>

Returns memoized solutions



121
122
123
124
# File 'lib/porolog/arguments.rb', line 121

def solutions(max_solutions = nil)
  @solutions ||= solve(max_solutions)
  max_solutions && @solutions[0...max_solutions] || @solutions
end

#solve(max_solutions = nil) ⇒ Array<Hash{Symbol => Object}>

Solves the Arguments



129
130
131
# File 'lib/porolog/arguments.rb', line 129

def solve(max_solutions = nil)
  @solutions = goal.solve(max_solutions)
end

#solve_for(*variables, max_solutions: nil) ⇒ Array<Object>

Extracts solution values.

Examples:

predicate :treasure_at
treasure_at(:X,:Y) << [...]
arguments = treasure_at(:X,:Y)
xs = arguments.solve_for(:X)
ys = arguments.solve_for(:Y)
coords = xs.zip(ys)


144
145
146
147
148
149
150
151
152
153
154
# File 'lib/porolog/arguments.rb', line 144

def solve_for(*variables, max_solutions: nil)
  variables = [*variables]
  solutions(max_solutions).map{|solution|
    values = variables.map{|variable| solution[variable] }
    if values.size == 1
      values.first
    else
      values
    end
  }
end

#valid?Boolean



157
158
159
# File 'lib/porolog/arguments.rb', line 157

def valid?
  !solutions(1).empty?
end

#variablesArray<Symbol>



107
108
109
# File 'lib/porolog/arguments.rb', line 107

def variables
  @arguments.map(&:variables).flatten.uniq
end