The logical variable

\prologindex{logical variable} Apart from being a means to establish the satisfiability of a goal, the power of logic programming lies in the way values are computed during an inference. The output of a goal with variables is a substitution binding these variables to terms. Terms are the elements of the universe a logic program deals with. As we have seen in section programs, defining logic programs, terms are either constants, variables or compound terms consisting of a function-symbol and zero or more argument-terms. We may use a logic program to define terms in a formal way. The program \oprog{terms}{
   constant(0) <-
   term(X) <- constant(X)
   term(s(X)) <- term(X)
  
} assumes a constant 0 and a one-argument function-symbol s and defines terms in accordance with the definition given earlier. The goal <- term(X) has as solutions all the possible bindings of X to the terms contained in the set [] { 0, s(0), s(s(0)),... } which represents the so-called Herbrand universe of the program terms. The possible output that may result from evaluating the goal <- term(X) is given by the substitutions [] {X/0}, {X/s(0)}, {X/s(s(0))},... binding X to the elements of the Herbrand universe. The question that we will answer in this section is how we are able to find these substitutions.

Substitutions

\prologindex{substitutions} Recall that a substitution %h is (represented by) a set of the form {X1/t1,...,X_k/t_k} that binds each variable X_i to a term t_i, for i=1,...,k. Applying a substitution %h to a term is recursively defined by \hspace{0.7cm} \begin{tabular}{l l} c %h = c & for a constant c, \\ X %h = t & for %h = {...,X/t,...} and X otherwise, and \\ f(t1,...,t_n) %h = f(t1%h,...,t_n%h) & for a compound term f(t1,...,t_n) \end{tabular} In other words, applying a substitution to a constant has no effect. Applying a substitution %h to a variable X results in the term t when the binding X/t occurs in %h. Applying a substitution %h to a compound term f(t1,...,t_n) results in the term f(t1 %h,...,t_n %h) in which %h is applied recursively to the argument terms t1,...,t_n. As an example, applying the substitution %h = {X/s(0)} gives [] $0%h = 0, X %h = s(0), Y %h = Y, and s(X)%h = s(s(0)) The application of a substitution is easily generalized to literals, by applying the substitution to each argument of the atom, and to conjunctions of literals, by applying the substitution to each literal. A substitution %h_2 is incompatible with a substitution %h_1 if there is a binding X/t1 in %h_1 and a binding X/t2 in %h_2 for which t1%h_2 != t2. For %h_2 compatible with %h_1, the composition %h_1 %h_2 of the substitutions %h_1 = {{X1/t1,...,X_n/t_n}} and %h_2 = {{Y1/t1',...,Y_k/t_k'}} is given by the set {{X1/t1%h_2,...,X_n/t_n%h_2,Y1/t1',..., Y_k/t_k'}}. If %h_2 is not compatible with %h_1, we say that the composition %h_1 %h_2 does not exist. For an arbitrary term t it holds that $(t%h_1)%h_2 = t ( %h_1 %h_2 ). Moreover, it is easy to check that the composition of substitutions is associative, that is that $(%h_1 %h_2) %h_3 = %h_1 (%h_2 %h_3). As an example, consider the composition of %h_1 = {{ X/s(X1) }} and %h_2 = {{ X1/s(X2) }} which results in %h_1 %h_2 = {{X/s(s(X2)), X1/s(X2)}}.

Unification

\prologindex{unification} Substitutions are the result of unifying two terms. A substitution
%h is a unifier of the terms t1 and t2 whenever t1%h = t2%h, that is when the terms become equal after applying %h. The most general unifier of two terms is the smallest substitution unifying the two terms. For example, the substitution %h = {{X/s(0)}} is the most general unifier of the terms f(X,Y) and f(s(0),Y). However, the substitution %h' = {{X/s(0), Y/0}} is also a unifier, but clearly less general since it may be derived from %h by adding the binding for Y. in other words, a substitution %h is called the most general unifier of two terms, or mgu for short, if for each unifier %s of these two terms there is a substitution %g such that %s = %h %g. A most general unifier can always be refined by another substitution to give an arbitrary unifier. Most general unifiers are not necessarily unique, but may be identified by renaming variables. We will describe a simple recursive algorithm to decide whether two terms are unifiable and to compute the most general unifier if it exists. To indicate that two terms are not unifiable we use the value fail. We now write the composition of substitutions %h_1 and %h_2 explicitly as %h_1 \c %h_2 and adopt the convention that %h_1 \c fail = fail and fail \c %h_2 = fail. Also when %h_2 is incompatible with %h_1, because they disagree on the binding for a variable, we define the composition %h_1 %h_2 = fail. We will use the constant %e to denote the empty substitution, for which it holds that %h \c %e = %e \c %h = %h for arbitrary %h. The algorithm is given by the following recursive equations [D unify(c1,c2) = %e if c1 = c2, unify(X,t) = {{X/t}} if X does not occur in t, unify(t,X) = unify(X,t) if t is not a variable, unify(f(t1,...,t_n), f(t1',...,t_n')) = unify(t1,t1') \c ... \c unify(t_n,t_n'), and unify(t1,t2) = fail otherwise D] Unifying two constants results in the empty substitution whenever the constants are equal. In case one of the terms is a variable X, a substitution binding X to the other term is delivered, provided that X does not occur in that term. Unifying two compound terms is possible only when the two terms have the same function-symbol and the same number of arguments. The result is the composition of the substitutions resulting from the pairwise unification of the argument terms. This leads to failure whenever such unification proves to be impossible or an incompatibility arises. The unification function delivers fail when none of these cases apply. As examples consider [D unify(p(s(X),0), p(Y,Z)) = {{Y/s(X)}} \c {{Z/0}} = {{ Y/s(X), Z/0 }} unify(p(s(X),0), p(Y,X)) = {{Y/s(X)}} \c {{X/0}} = {{ Y/s(0), X/0 }} unify(p(s(X),0), p(Y,s(Z))) = {{Y/s(X)}} \c fail = fail unify(p(s(X),0), p(Y,Y)) = {{Y/s(X)}} \c {{Y/0}} = fail D] In the last example fail ~ results because an incompatibility arises between the substitutions resulting from unifying the argument terms, since they disagree on the binding of the variable Y.

The occur-check

\prologindex{occur check} In the unification algorithm, a binding results whenever we encounter a variable X and a term t, provided that X does not occur in t. In actual logic programming systems this so-called occur-check is often omitted for reasons of efficiency. This may lead to anomalous behavior, as exemplified by the goal
<- X = s(X) which succeeds, resulting in the binding {{X/s(X)}}, although it clearly has no solution.

Compound terms

\prologindex{compound terms} In logic programming systems, unification provides a uniform mechanisms for parameter passing, data selection and data construction. Terms can be used to package data in a way resembling records. For instance, the fields on a chessboard can be denoted by the terms []
position(1,1), position(1,2),..., position(8,8) naming the index in respectively the row and the column of the board. In a program the pattern of this structure can be used to select the wanted information, as illustrated in the clause that tests whether two positions occur on the same row. \oprog{row}{
   same_row(position(X,Y), position(X,Z)) <- 
  
} When evaluating a goal {\em same_row} containing two positions, the information concerning the rows is selected and the implicit constraint that the rows are equal, since they are both referred to by the variable X, is enforced by the unification procedure.