File: ssu.doc

package info (click to toggle)
swi-prolog 9.0.4%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 82,408 kB
  • sloc: ansic: 387,503; perl: 359,326; cpp: 6,613; lisp: 6,247; java: 5,540; sh: 3,147; javascript: 2,668; python: 1,900; ruby: 1,594; yacc: 845; makefile: 428; xml: 317; sed: 12; sql: 6
file content (305 lines) | stat: -rw-r--r-- 10,544 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
\section{Single Sided Unification rules}
\label{sec:ssu}

\index{single sided unification}%
\index{SSU}%
\index{term subsumption}%
For the execution of a normal Prolog clause, the goal term is unified
with the head of the clause. This allows us to write facts such as below
and use this relation in all four possible \jargon{modes}.  This is the basis
of SLD resolution that turns Prolog into a logic programming language.

\begin{code}
parent('Bob', 'Susan').
\end{code}

In practice though, Prolog is both a logic programming language and a
language for expressing computations in a near \jargon{procedural}
style. The first is used to solve (notably) combinatorial problems while
the latter is used for I/O, data transformation and the many non-logical
operations that are involved in many applications.

Many Prolog programmers experience writing procedural style Prolog as
fighting non-determinism and dealing with hard to debug silent failures
because no clause matches some goal. Below are two typical queries on
library predicates that have a procedural nature, i.e., are
\jargon{single moded}.

\begin{code}
?- sum_list(a, X).
false.

?- sum_list([1|T], X).
T = [],
X = 1 ;
ERROR: Arguments are not sufficiently instantiated
\end{code}

The definition of sum_list/2 is it appears in library(lists) is below.
This implementation can be considered elegant. Note that sum_list/2 has
only one meaningful mode: (+,-). A general (logical) implementation
would allow for a partial list or a list holding one or more variables,
With a proper list that holds a single variable we can still make a
sound logical implementation. In all other cases the number of solutions
is infinite and even \jargon{uncountable} for a partial list, making the
predicate useless as a \jargon{generator} of solutions.


\begin{code}
sum_list(Xs, Sum) :-
    sum_list(Xs, 0, Sum).

sum_list([], Sum, Sum).
sum_list([X|Xs], Sum0, Sum) :-
    Sum1 is Sum0 + X,
    sum_list(Xs, Sum1, Sum).
\end{code}

If we want to avoid the above dubious behaviour we have two options.
First, we can verify that the first argument is a list before entering
the recursion, changing the first clause as below.  The disadvantage
is that we process the list twice.


\begin{code}
sum_list(Xs, Sum) :-
    must_be(list, Xs),
    sum_list(Xs, 0, Sum).
\end{code}

Alternatively, we can rewrite the second clause to verify the list on
the fly. That leads to the code below. Most likely the overhead of this
alternative compared to the above is even worse in many Prolog
implementations. Most people would also consider this code rather
inelegant.


\begin{code}
sum_list(Var, _, _) :-
    var(Var),
    instantiation_error(Var).
sum_list([], Sum, Sum) :-
    !.
sum_list([X|Xs], Sum0, Sum) :-
    !,
    Sum1 is Sum0 + X,
    sum_list(Xs, Sum1, Sum).
sum_list(NoList, _, _) :-
    type_error(list, NoList).
\end{code}

Another example is a relation \nopredref{max}{3}, expressing the maximum of two
numbers. A classical textbook definition could be as below. This code
has two drawbacks. First it leaves an open choice points in most Prolog
implementations if \arg{X} is the largest and second it compares the two
numbers twice. Some Prolog systems detect this particular case, but in
general it needs two know that one test is the strict negation of the
other.

\begin{code}
max(X,Y,X) :- X >= Y.
max(X,Y,Y) :- Y > X.
\end{code}

As a result people use a cut and might come up with the \textbf{wrong}
solution below. Consider the query \exam{?- max(5,2,2).} to see why this
code is broken.

\begin{code}
max(X,Y,X) :- X >= Y, !.
max(_,Y,Y).
\end{code}

A correct solution is below, \jargon{delaying} binding the output until after
the cut.

\begin{code}
max(X,Y,M) :- X >= Y, !, M = X.
max(_,Y,Y).
\end{code}

Some people may prefer using if-then-else as below.  This is arguable
the cleanest efficient solution in standard Prolog.

\begin{code}
max(X,Y,M) :- ( X >= Y -> M = X ; M = Y ).
\end{code}

As we have seen from these examples, writing procedural code in Prolog
requires us to follow the two basic principles below. Both principles
have been properly described in \textit{The Craft of Prolog}
\cite{Keefe:90}.

\begin{itemize}
\item
    Structure every clause as \exam{Head :- Guard, !, Body}. Every
    clause has the cut as early as possible. \arg{Guard} can be empty.
    The last clause often does not need a cut.

\item
    Avoid that the head unification binds values in the goal term.  We
    see this may lead to undesirable results such as sum_list(L,S) binding
    \arg{L} to `[]` and \arg{S} to `0` as well as loss of \jargon{steadfastness}, causing
    max(5,2,2) to succeed.  The first requires additional var/1 or nonvar/1
    tests. The second requires delaying unification until after the cut.
\end{itemize}


\href{http://picat-lang.org/}{Picat} provides the \funcref{=>}{2}
alternative for the Prolog \jargon{neck} (\funcref{:-}{2}) to force the
above practices. A Picat rule has the following shape:

\begin{code}
Head, Guard => Body.
\end{code}

This is semantically equivalent to the Prolog clause below. The
subsumes_term/2 guarantees the clause head is more \jargon{generic} than the
goal term and thus unifying the two does not affect any of the arguments
of the goal. This implies all output unification _must_ be done after
the head unification.

\begin{code}
p(V1,V2,...,Vn) :-
    Pattern = p(A1,A2,...,An),
    Args = p(V1,V2,...,Vn),
    subsumes_term(Pattern, Args),
    Pattern = Args,
    Guard,
    !,
    Body.
\end{code}

SWI-Prolog as of version 8.3.19 support \funcref{=>}{2} as an alternative to
normal Prolog clauses. The construct comes with the following
properties.

\begin{itemize}
\item
   A predicate either uses \funcref{:-}{2} for all its clauses or
   \funcref{=>}{2}. Mixing is \textbf{not} allowed and raises a
   permission error for a clause that does not use the same
   \jargon{neck} as the first clause.
\item
   Unlike Picat, it is an error if no clause matches.
\end{itemize}

Given \funcref{=>}{2} rules, we can rewrite sum_list/2 as below. The
first clause can be written using \funcref{:-}{2} or \funcref{=>}{2}. As
the head is the most general head and there is only one clause these are
fully equivalent. The \nopredref{sum_list}{3} helper needs a small
modification: we need to delay the unification against \arg{Sum} to the
body. The last clause is equivalent.


\begin{code}
sum_list(Xs, Sum) =>
    sum_list(Xs, 0, Sum).

sum_list([], Sum0, Sum) =>
    Sum = Sum0.
sum_list([X|Xs], Sum0, Sum) =>
    Sum1 is Sum0 + X,
    sum_list(Xs, Sum1, Sum).
\end{code}

Given this definition, \exam{sum_list(L,S)} no longer matches a rule
and neither does e.g., \exam{sum_list(a,S)}. Both raise an error.
Currently the error is defined as below.

\begin{code}
existence_error(matching_rule, Head)
\end{code}

Should silent failure be desired if no rule matches, this is easily
encoding by adding a rule at the end using the most general head and
fail/0 as body:

\begin{code}
sum_list(_,_,_) => fail.
\end{code}

\subsection{Single Sided Unification Guards}
\label{sec:ssu-guard}

Using the construction \exam{Head, Guard => Body}, the \arg{Guard} is
executed \emph{after} the single sided head unification. If the
\arg{Guard} succeeds the clause executes a cut (\predref{!}{0}) and
proceeds normally. There are no restrictions on the guard code. A well
behaved guard is a \jargon{test}.  Notably:

\begin{itemize}
    \item Though not enforced\footnote{We do not know about an efficient
way to enforce unification against head arguments}, guard code shall
not instantiate variables in the \arg{Head} because this breaks the
promise of SSU and may make the node non-steadfast.
    \item It is bad style (but again, not enforced) to have any type
of side effects (output, database change, etc.)
    \item Typically, guard calls are \const{semidet}.  Non-deterministic
calls are allowed.  If the guard succeeds with choicepoints these are
pruned before the body is entered.
\end{itemize}

As a special exception, explicit unification against a variable in the
head is moved into the head. See \secref{indexbody}. In the example
below, the \exam{X = f(I)} is moved into the head and (thus) is executed
using single sided unification.

\begin{code}
p(X), X = f(I), integer(I) => q(X).
\end{code}

\begin{quote}
\textbf{Warning} Moving the guard unification into the head changes the
semantics of the unification. This may be defended by the rules above
that claim one should not unify against the head arguments in the guard.
Future versions may use a dedicated operator to indicate that the
unification may be moved into the head.
\end{quote}


\subsection{Consequenses of {=>} single sided unification rules}
\label{sec:ssu-consequences}

The \funcref{=>}{2} construct is handled by the low-level compiler if no
\jargon{guard} is present. If a guard is present it is currently compiled into
the construct below. The Picat \funcref{?=>}{2} \jargon{neck} operator is like
\funcref{=>}{2}, but does not \jargon{commit} to this rule. We are not
yet sure whether or not SWI-Prolog will remain supporting
\funcref{?=>}{2}.\footnote{\funcref{?=>}{2} is currently implemented but
not defined as an operator.}

\begin{code}
Head ?=> Guard, !, Body.
\end{code}

The main consequence is that clause/2 cannot distinguish between a
normal clause and a \funcref{=>}{2} clause. In the current implementation it
operates on both without distinguishing the two. This implies e.g.,
\jargon{cross referencing} still works. Meta interpretation however
does not work. In future versions clause/2 may fail on these rules. As
an alternative we provide rule/2,3.

\begin{description}
    \predicate{rule}{2}{:Head, -Rule}
\nodescription
    \predicate{rule}{3}{:Head, -Rule, -Ref}
True when \arg{Rule} is a rule/clause that implements \arg{Head}.
\arg{Rule} is a complete rule term.  For a normal clause this is
a term \exam{Head :- Body} and for a single sided unification rule
it is a term \exam{Head {=>} Body}.
\end{description}


\subsection{SSU: Future considerations}
\label{sec:ssu-future}

The current implementation is a rather simple. Single sided unification
is achieved doing normal head unification and backtrack if this
unification bound variables in the goal term. Future versions are likely
to backtrack as soon as we find a variable in the goal that needs to be
unified.

It is likely that in due time significant parts of the libraries will be
migrated to use SSU rules, turning many silent failures on type errors
into errors.