Builtin Predicates - wizardofosmium/porolog GitHub Wiki

Porolog Builtin Predicates

write(*args)

Outputs all arguments with two conversions:

  • Symbols are replaced by their instantiated value or their name if uninstantiated, and
  • Arrays are output using inspect

Calls $stdout.print.

writenl(*args)

Functions like write except a new line is added.

Calls $stdout.puts.

nl(goal, block)

Outputs a new line.

Calls $stdout.puts.

is(variable, *args, &is_block)

Instantiates, if possible, the variable to the result of the provided block. The block is passed the values of the arguments. For example, if you want to instantiate Y to the value of X plus one

  is(:Y, :X) {|x| x + 1 }

If X is instantiated, then x will be its value. If X is uninstantiated, then x + 1 will raise an exception. To guard against this, you can either ensure X is instantiated by logic or check it is instantiated in Ruby code in the block. In logic, you could simply use:

  nonvar(:X),
  is(:Y, :X) {|x| x + 1 }

In Ruby, you can test if it is a variable

  is(:Y, :X) {|x| x.type != :variable && x + 1 }

and when the result of the block is nil or false, the is goal will fail, triggering backtracking.

If Y is uninstantiated, then it is instantiated with the result of the block, unless it is nil or false, and the goal succeeds. If Y is already instantiated, then the is goal only succeeds if Y can be unified with the result of the block.

var(variable)

Succeeds if the argument is an uninstantiated variable; otherwise it fails.

nonvar(variable)

Succeeds if the argument is not an uninstantiated variable; otherwise it fails.

atom(variable)

Succeeds if the argument is a String; otherwise it fails.

atomic(variable)

Succeeds if the argument is not an uninstantiated variable and not an Array; otherwise it fails.

integer(variable)

Succeeds if the argument is an Integer; otherwise it fails.

eq(x, y)

If the arguments are nonvars, their equality is checked.

eq([3,2,1,4], [1,2,3,4])  # fails
eq([1,2,3,4], [1,2,3,4])  # succeeds

Uninstantiated variable arguments succeed if they are bound together.

eq(:X, :X)  # succeeds
eq(:X, :Y)  # depends on whether X and Y are bound directly or indirectly

Uninstantiated variables are not instantiated.

eq([1,2,3,4], :L)  # fail
eq(:L, [1,2,3,4])  # fail

is_eq(x, y)

Implements a synthesis of is and eq, instantiating the left hand side variable if possible.

is_eq([1,2,3,4], :L)  # fail unless L is instantiated to [1,2,3,4]
is_eq(:L, [1,2,3,4])  # succeed and instantiate L to [1,2,3,4] if L is uninstantiated

ruby(*args, &ruby_block)

Allows Ruby code to be inserted into rules. The ruby predicate succeeds by default. To make a ruby predicate fail, return the Symbol :fail.

ruby(:L, :name) {|l, name|
  CSV.open(name, 'w') do |csv|
    l.each do |element|
      csv << [*element]
    end
  end
}

ruby(:X) {|x|
  $stdout.puts "x is now #{x.inspect}, forcing backtracking ..."
  :fail
}

noteq(x, y)

If the arguments are nonvars, their equality is checked.

noteq([3,2,1,4], [1,2,3,4])  # succeeds
noteq([1,2,3,4], [1,2,3,4])  # fails

Uninstantiated variable arguments fail if they are bound together.

noteq(:X, :X)  # fails
noteq(:X, :Y)  # depends on whether X and Y are bound directly or indirectly

Uninstantiated variables are not instantiated but do succeed.

noteq([1,2,3,4], :L)  # succeed
noteq(:L, [1,2,3,4])  # succeed

is_noteq(variable, all_values, *exclusions)

is_noteq implements a simple constraint mechanism. Its purpose is to provide an efficiency when dealing with simple constraints where:

  • the variable must be one of the values of all_values,
  • the variable must not be one of the values of exclusions, and
  • additionally, all exclusions must be unique values.

Then, it functions as member(variable, all_values_less_exclusions).

is_noteq(:X, [1,2,3,4,5], :Y, :Z)

The above succeeds if Y and Z are different and then successively instantiates X to each of the remaining values except the values of Y and Z. If X is already instantiated, compares membership instead of instantiating.

less(x, y)

The first argument must be less than the second argument.

less(4, 7)         # succeed
less('b', 'h')     # succeed
less(1.003, 1.01)  # succeed
less(7, 4)         # fail
less('h', 'b')     # fail
less(1.01, 1.003)  # fail
less(:X, :X)       # fail

gtr(x, y)

The first argument must be greater than the second argument.

gtr(7, 4)         # succeed
gtr('h', 'b')     # succeed
gtr(1.01, 1.003)  # succeed
gtr(4, 7)         # fail
gtr('b', 'h')     # fail
gtr(1.003, 1.01)  # fail
gtr(:X, :X)       # fail

lesseq(x, y)

The first argument must be less than or equal the second argument. Uninstantiated variables fail unless they are bound to each other.

lesseq(:X, :X)  # succeed

gtreq(x, y)

The first argument must be greater than or equal the second argument. Uninstantiated variables fail unless they are bound to each other.

gtreq(:X, :X)  # succeed

length(list, length)

length() succeeds if the length of the list is equal to the value of the second argument. If length is uninstantiated, then it is instantiated to the length of the list. If list is uninstantiated, then it is instantiated to a list of length length of anonymous variables.

between(variable, lower, upper)

Succeeds if variable is in the inclusive range of lower to upper. If variable is uninstantiated, it is instantiated to each value of the range on backtracking. If lower or upper are uninstantiated, between instantiates them to create the narrowest range.

between(:I, 1, 10)
between(4, 1, :upper)        # upper --> 4
between(4, :lower, 10)       # lower --> 4
between(-7, 1, :upper)       # fail
between(24, :lower, 10)      # fail
between(24, :lower, :upper)  # lower --> 24, upper --> 24

member(element, list, limit = 100)

Succeeds when element is a member of list. If variables are present, member tries to instantiate the variables to produce a successful result; otherwise it fails. If element is uninstantiated, it is successively instantiated to each member of the list.

          assert_solutions  member(3, [1,2,:C,4]),   [{ C: 3 }]
          assert_solutions  member([:A,3], [1,2],[2,3],[3,4](/wizardofosmium/porolog/wiki/1,2],[2,3],[3,4)),   [{ A: 2 }]
          assert_solutions  member(:M, [1,2],[2,3],[3,4](/wizardofosmium/porolog/wiki/1,2],[2,3],[3,4)),   [

If list is uninstantiated, then the third parameter sets the limit of the number of solutions where list is instantiated to lists where element is in each subsequent position in each list. For example,

member(:X, :L, 4)

produces

L --> [X, ...]
L --> [_, X, ...]
L --> [_, _, X, ...]
L --> [_, _, _, X, ...]

append(front, back, front_back)

Succeeds when the front list appended with the back list equals the front_back list. Any variables are instantiated to satisfy this condition. It fails when the lists are not equal.

If there are multiple ways to satisfy the condition, each solution is successively instantiated on backtracking. For example,

append(:M, :L, [1,2,3,4,5])

produces

M --> [],              L --> [1, 2, 3, 4, 5]
M --> [1],             L --> [2, 3, 4, 5]
M --> [1, 2],          L --> [3, 4, 5]
M --> [1, 2, 3],       L --> [4, 5]
M --> [1, 2, 3, 4],    L --> [5]
M --> [1, 2, 3, 4, 5], L --> []

append() also binds front, back, and front_back so that if they are instantiated later, they will still obey the append condition.

permutation(list1, list2)

Succeeds when the list arguments are a permutation of each other. Embedded and direct variables will be instantiated to satisfy the condition. Where there are multiple possible instantiations, each will be successively instantiated on backtracking. For example,

permutation([:A, 2, 1, 4], [1, 2, :C, 4])

produces

A --> 1,   C --> 1
A --> 2,   C --> 2
A --> nil, C --> nil
A --> 4,   C --> 4

Note: nil indicates an uninstantiated variable.

reverse(list1, list2)

Succeeds when the list arguments are the reverse of each other. Embedded and direct variables will be instantiated to satisfy the condition.