Custom Predicates - wizardofosmium/porolog GitHub Wiki
Custom Predicates
The aim of Porolog is not to provide an interface to a Prolog instance but rather to provide the power of declarative logic to Ruby as though it were a part of Ruby, and thus allow intermixing of declarative logic in the same way that functional programming can be intermixed in Ruby.
There are three main levels of adding Ruby into the logic:
- in an
is
predicate call, - in a
ruby
predicate call, and - in a custom builtin predicate.
is
Ruby Code in an If you want the result of a Ruby block to be inserted into the logic,
you can use an is
predicate call. The result is instantiated to the
target variable. If the variable is already instantiated, then the result
is unified with the variable, and thus the goal may fail if the values
are incompatible.
builtin :is
predicate :get_ruby_result
get_ruby_result(:X) << [
is(:X) {
# ... Ruby code here ...
}
]
See: https://github.com/wizardofosmium/porolog/wiki/Builtin-Predicates#isvariable-args-is_block
for more details on is
.
ruby
Ruby Code in a If you are not specifically wanting a result from Ruby but rather just some code to be executed,
then you can use a ruby
predicate call instead of an is
predicate call.
builtin :ruby
predicate :do_ruby_actions
do_ruby_actions() << [
ruby() {
# ... Ruby code here ...
}
]
See: https://github.com/wizardofosmium/porolog/wiki/Builtin-Predicates#rubyargs-ruby_block
for more details on ruby
.
Ruby Code in a Custom Builtin Predicate
If you want your code DRYer, you could simply use a method call to DRY up your code. However, if it is more convenient to have your code as a custom builtin predicate, you can do that by declaring a method as a builtin predicate.
You need to define the method in Porolog::Predicate::Builtin
.
The first two parameters need to be:
goal
, which is the current goalblock
, which is the logic stack
For the logic tree to be continued to be traversed, you need to call the block
if the predicate has been satisfied and return its result, or return false
if the predicate has not been satisfied.
Arguments and also a block can be provided to the custom predicate if desired.
The goal
and block
parameters are required but you can otherwise define the parameters
as a normal Ruby method. The custom builtin predicate can now be part of the backward
chaining process.
module Porolog
class Predicate
module Builtin
def custom(goal, block, *args, &arg_block)
# ... Ruby code here ...
block.call(goal) || false
end
end
end
end
Quadratic Solver
This is an example of using the quadratic equation as a custom builtin.
require 'porolog'
include Porolog
module Porolog
class Predicate
module Builtin
def quadratic(goal, block, x, a, b, c)
a = a.value.value
b = b.value.value
c = c.value.value
return false if [a,b,c].any?{|v| v.type == :variable }
return false unless x.type == :variable
satisfied = false
solutions = []
if a != 0
d = b ** 2 - 4 * a * c
if d == 0
solutions << (-b / 2 / a)
else
if d > 0
solutions << ((-b - Math.sqrt(d)) / 2 / a)
solutions << ((-b + Math.sqrt(d)) / 2 / a)
end
end
end
solutions.each do |solution|
instantiation = x.instantiate(solution)
instantiation && block.call(goal) && (satisfied = true)
instantiation&.remove
return satisfied if goal.terminated?
end
satisfied
end
end
end
end
builtin :quadratic, :gtr
predicate :use_quadratic, :positive_solutions
use_quadratic(:X, :A, :B, :C) << [
quadratic(:X, :A, :B, :C)
]
positive_solutions(:X, :A, :B, :C) << [
quadratic(:X, :A, :B, :C),
gtr(:X, 0)
]
puts use_quadratic(:X, 1, 1, 12).solve.inspect
puts use_quadratic(:X, 1, 1, -12).solve.inspect
puts use_quadratic(:X, 2, 4, 2).solve.inspect
puts use_quadratic(-1, 2, 4, 2).solve.inspect
puts use_quadratic(1, 2, 4, 2).solve.inspect
puts use_quadratic(3, 1, 1, -12).solve.inspect
puts positive_solutions(:X, 1, 1, -12).solve.inspect
Combinations
This is an example of defining a custom builtin predicate, similar to permutation
that returns combinations instead of permutations.
require 'porolog'
include Porolog
module Porolog
class Predicate
module Builtin
def combination(goal, block, list, size, combination)
list = list.value.value
size = size.value.value
return false unless list.type == :array
return false unless size.is_a?(Integer)
return false unless combination.type == :variable
satisfied = false
list.combination(size).each do |array|
instantiation = combination.instantiate(array)
instantiation && block.call(goal) && (satisfied = true)
instantiation&.remove
return satisfied if goal.terminated?
end
satisfied
end
end
end
end
builtin :combination, :length, :between
predicate :use_combination, :subset
use_combination(:List, :Size, :Combination) << [
combination(:List, :Size, :Combination)
]
subset(:Subset, :Set) << [
length(:Set, :Size),
between(:Subset_size, 0, :Size),
combination(:Set, :Subset_size, :Subset)
]
puts use_combination([1,2,3,4,5], 3, :C).solve.inspect
puts subset(:Subset, [1,2,3,4,5]).solve.inspect