Previous Table of Contents Next


6.3.11.1. A First Example

For instance, say that we wanted to represent a square on a game board. We could use the notation we adopted before, sq(Row,Column), but it would stand out more in our program if we could write Row::Column. We can achieve this with the following query declaring the symbol :: as an operator:

   op(500, xfy, ::).

As with the dynamic declaration (see section 6.3.10.2), we can achieve the same effect by putting the following line in a source file and consulting it:

   :- op(500, xfy, ::).

The first argument of the op predicate is the precedence of the operator, which we will look at more soon. The second argument is a fixity and associativity specifier; all we need to know about this at the moment is that xfy declares an infix operator. The third and last argument is the operator to be declared.

Immediately after processing one of these declarations, Row::Column is parsed by Prolog as a term and can appear anywhere a term can—as an argument to a predicate, for instance. Its internal representation is the same as if we typed ::(Row,Column), and in fact, the two terms are equal as far as Prolog is concerned. But terms of the form ::(Row,Column), after the declaration, can always be input and will always be output in the infix form.

6.3.11.2. What Can Be an Operator?

What sequences of characters can appear in the third argument of an op declaration? Actually, it turns out that the more relevant question is What sequence of characters can be a function symbol?

So far we have looked only at function symbols that start out with a lowercase letter and contain only alphanumeric characters and underscores, or the single-quoted sequences of characters. In fact there is a third class: all sequences of symbol characters. The set of symbol characters varies from one Prolog to another but typically excludes the alphanumeric characters and all forms of bracketing and includes such things as the colon, semicolon, slash, plus, and minus characters. We cannot mix symbol and non-symbol characters in a function symbol name, precisely because we would like to be able to write something such as a+b with no spaces and have the parser parse it as three separate tokens.

In fact, any function symbol can appear in the third argument of the op predicate. If we wanted to represent regular expressions in Prolog, for instance, we might want to use Exp1 bar Exp2 to mean either Exp1 or Exp2 and use Exp1 then Exp2 to mean Exp1 followed by Exp2. We could achieve this with the declarations

   :- op(600, xfy, bar).
   :- op(500, xfy, then).

This would ensure that the expression X then Y bar Z would be parsed as exactly the same term as bar(then(X,Y),Z).

6.3.11.3. Precedences and Specifiers

Why, in the previous example, was X then Y bar Z parsed as bar(then(X,Y),Z) rather than as then(X,bar(Y,Z))? The answer is in the precedence argument. In a complex expression involving many operators, the rule of thumb is that the operator with the highest precedence is the one that is the outermost operator. Because bar has precedence 600, and then only precedence 500, bar wins as the outermost.

This extends to built-in, predeclared operators such as the arithmetic operators. For instance, + and are automatically declared by Prolog to be operators of precedence 500, and the * and / operators to have precedence 400, in order to achieve the desired grouping behavior.

As in arithmetic, we can always use parentheses to group operands in the way we would like. For instance, the term X then (Y bar Z) is always parsed as then(X,bar(Y,Z)), regardless of the precedences of the operators.

Now, what about the expression A then B then C? Is it parsed as then(A,then(B,C)) or as then(then(A,B),C)? The answer is the former, and the reason lies in the fixity and associativity specifier xfy. Each specifier is a tiny picture of where the function symbol (f) is in relation to the operands and what kind of operands it can have (x and y). The operand code x indicates an operand whose outermost operator has strictly lower precedence to the operator being declared, and y indicates an operand whose outermost operator has precedence lower than or the same as the operator being declared. When then is declared using the specifier xfy, the only way to parse A then B then C that is consistent with that picture is then(A,then(B,C)).

Thus xfy declares an operator as being right-associative. This is a natural choice for then as a regular expression operator because we probably want to process the leftmost element of a then chain separately first. However, it would not be a good choice for the arithmetic operator because we would like 10 - 6 - 4 to be parsed the same as (10 - 6) - 4. Hence the minus (and in fact all four of the standard arithmetic operators) are declared yfx instead, making them left-associative.

It is sometimes also useful to declare an operator as xfx, meaning that you cannot chain together sequences with that operator in between. This might have been a better choice for the Row::Column operator because we don’t want to use it in any situation other than separating two numbers. Note, however, that it doesn’t make sense to declare an operator as yfy because that would make terms such as A then B then C ambiguous. Hence yfy is not an allowed specifier.


Previous Table of Contents Next