Previous: Constrained polymorphic modes, Up: Modes   [Contents]


4.4 Different clauses for different modes

Because the compiler automatically reorders conjunctions to satisfy the modes, it is often possible for a single clause to satisfy different modes. However, occasionally reordering of conjunctions is not sufficient; you may want to write different code for different modes.

For example, the usual code for list append

append([], Ys, Ys).
append([X|Xs], Ys, [X|Zs]) :-
    append(Xs, Ys, Zs).

works fine in most modes, but is not very satisfactory for the ‘append(out, in, in)’ mode of append, because although every call in this mode only has at most one solution, the compiler’s determinism inference will not be able to infer that. This means that using the usual code for append in this mode will be inefficient, and the overly conservative determinism inference may cause spurious determinism errors later.

For this mode, it is better to use a completely different algorithm:

append(Prefix, Suffix, List) :-
    list.length(List, ListLength),
    list.length(Suffix, SuffixLength),
    PrefixLength = ListLength - SuffixLength,
    list.split_list(PrefixLength, List, Prefix, Suffix).

However, that code doesn’t work in the other modes of ‘append’.

To handle such cases, you can use mode annotations on clauses, which indicate that particular clauses should only be used for particular modes. To specify that a clause only applies to a given mode, each argument Arg of the clause head should be annotated with the corresponding argument mode Mode, using the ‘::’ mode qualification operator, i.e. ‘Arg :: Mode’.

For example, if ‘append’ was declared as

:- pred append(list(T), list(T), list(T)).
:- mode append(in, in, out).
:- mode append(out, out, in).
:- mode append(in, out, in).
:- mode append(out, in, in).

then you could implement it as

append(L1::in,  L2::in,  L3::out) :- usual_append(L1, L2, L3).
append(L1::out, L2::out, L3::in)  :- usual_append(L1, L2, L3).
append(L1::in,  L2::out, L3::in)  :- usual_append(L1, L2, L3).
append(L1::out, L2::in,  L3::in)  :- other_append(L1, L2, L3).

usual_append([], Ys, Ys).
usual_append([X|Xs], Ys, [X|Zs]) :- usual_append(Xs, Ys, Zs).

other_append(Prefix, Suffix, List) :-
    list.length(List, ListLength),
    list.length(Suffix, SuffixLength),
    PrefixLength = ListLength - SuffixLength,
    list.split_list(PrefixLength, List, Prefix, Suffix).

This language feature can be used to write “impure” code that doesn’t have any consistent declarative semantics. For example, you can easily use it to write something similar to Prolog’s (in)famous ‘var/1’ predicate:

:- mode var(in).
:- mode var(free>>free).
var(_::in) :- fail.
var(_::free>>free) :- true.

As you can see, in this case the two clauses are not equivalent.

Because of this possibility, predicates or functions which are defined using different code for different modes are by default assumed to be impure; the programmer must either (1) carefully ensure that the logical meaning of the clauses is the same for all modes, which can be declared to the compiler through a ‘pragma promise_equivalent_clauses’ declaration, or a ‘pragma promise_pure’ declaration, or (2) declare the predicate or function as impure. See Impurity.

In the example with ‘append’ above, the two ways of implementing append do have the same declarative semantics, so we can safely use the first approach:

:- pragma promise_equivalent_clauses(append/3).

The pragma

:- pragma promise_pure(append/3).

would also promise that the clauses are equivalent, but on top of that would also promise that the code of each clause is pure. Sometimes, if some clauses contain impure code, that is a promise that the programmer wants to make, but this extra promise is unnecessary in this case.

In the example with ‘var/1’ above, the two clauses have different semantics, so the predicate must be declared as impure:

	:- impure pred var(T).

Previous: Constrained polymorphic modes, Up: Modes   [Contents]