Article View: comp.lang.prolog
Article #3374Re: Clause fusion
From: Saumya Debray
Date: Mon, 16 May 1988 19:23
Date: Mon, 16 May 1988 19:23
165 lines
6102 bytes
6102 bytes
In article <983@cresswell.quintus.UUCP>, Richard A. O'Keefe writes
about the "obvious" optimization of clause fusion:
> The original code was
>
> association(Stack, Key, Val, absent) :-
> is_empty(Stack),
> !.
> association(Stack, Key, Val, Result) :-
> pop_stack(Key1, Val1, Stack, Stack1),
> Key1 \== Key,
> !,
> association(Stack1, Key, Val, Result).
> association(Stack, Key, Val, present) :-
> pop_stack(Key1, Val1, Stack, Stack1),
> Key1 == Key,
> Val1 = Val,
> !.
> association(Stack, Key, Val, conflict).
Richard points out several problems with a proposed solution. It
turns out, however, that the problems are not with Clause Fusion itself,
but rather with some subsequent transformations involving moving cuts
around a clause. It shouldn't be surprising that this is not an easy
thing to do in general.
It's interesting to transform this code a little at a time, to
see what assumptions are necessary at each step in order to preserve
equivalence. To reduce clutter as far as possible in what follows,
I'll consider only the clauses that Richard fused, and ignore the first
clause in this definition.
--------------------
STEP 1: No assumptions: all we can do is move some arguments
from the head to just beyond the neck of the clause, to get:
association(Stack, Key, Val, Result) :-
(pop_stack(Key1, Val1, Stack, Stack1),
Key1 \== Key,
!,
association(Stack1, Key, Val, Result)
) ;
(Result = present,
pop_stack(Key1, Val1, Stack, Stack1),
Key1 == Key,
Val1 = Val,
!
) ;
Result = conflict.
--------------------
STEP 2: Assume that association/4 is always called with its
last argument free. This allows us to delay the unification
"Result = present" in the second disjunct, to get the disjuncts:
(pop_stack( ... ), Key1 \== Key, ... ) ;
(pop_stack( ... ), Key1 == Key, Val1 = Val, !, Result = present) ;
Result = conflict.
--------------------
STEP 3: Assume pop_stack/4 is free of side effects. This allows us to
factor the literal for pop_stack/4 in the disjunct, producing
association(Stack, Key, Val, Result) :-
(pop_stack(Key1, Val1, Stack, Stack1),
(Key1 \== Key,
!,
association(Stack1, Key, Val, Result)
) ;
( /* Key1 == Key, */
Val1 = Val,
!
Result = present
)
) ;
Result = conflict.
We can't do much more at this point. Note that the inner disjunction
can't really be converted to an if-then-else, because the cut is necessary
to cut away the alternative "Result = conflict" in the outer disjunct.
Notice also that even if pop_stack/4 is known to be deterministic, we can't
transform this to
pop_stack( ... ) ->
( ... ) ;
Result = conflict.
(Consider the case where pop_stack/4 succeeds, "Key1 \== Key" fails, and
"Val1 = Val" fails.)
--------------------
STEP 4: Richard assumed, in addition, that association/4 is always called
with its first three arguments ground terms. Looking at the code, it seems
to me that the predicate is intended to search a (given) stack for a value
associated with a (given) key, so I think it's more reasonable to assume
that the third argument to association/4 is also free in each call to it.
If we make this assumption, then the unification "Val1 = Val" (after
"Key1 == Key") can also be delayed until after the cut. This transforms
the inner disjunct to
(Key1 \== Key, !, association( ... ) ) ;
(!, Val1 = Val, Result = present)
This, with some easy massaging, yields
association(Stack, Key, Val, Result) :-
(pop_stack(Key1, Val1, Stack, Stack1),
(Key1 \== Key ->
association(Stack1, Key, Val, Result) ;
(Val1 = Val,
Result = present
)
),
!
) ;
Result = conflict.
--------------------
STEP 5: Assume that association/4 is deterministic. This allows us to
bubble the cut backwards, eventually yielding
association(Stack, Key, Val, Result) :-
(pop_stack(Key1, Val1, Stack, Stack1),
!,
(Key1 \== Key ->
association(Stack1, Key, Val, Result) ;
(Val1 = Val,
Result = present
)
)
) ;
Result = conflict.
It's now a simple matter to reformat this to
association(Stack, Key, Val, Result) :-
pop_stack(Key1, Val1, Stack, Stack1) ->
(Key1 \== Key ->
association(Stack1, Key, Val, Result) ;
(Val1 = Val,
Result = present
)
) ;
Result = conflict.
--------------------
[ If we don't want to assume that association/4 is always called with
its third argument uninstantiated, then different (and, in my opinion,
less reasonable) assumptions are necessary to take the transformation
beyond step 3 above. This article's getting too long for that. ]
The resulting code is seen to be quite different from the proposed
> association(Stack, Key, Val, Result) :-
> pop_stack(Key1, Val1, Stack, Stack1),
> ( Key1 \== Key ->
> association(Stack1, Key, Val, Result)
> ; Val1 = Val ->
> Result = present
> ; Result = conflict
> ).
To sum up: the problems mentioned by Richard are not with clause fusion
per se, but rather with some subsequent transformations involving
moving cuts around a clause. Not surprisingly, this is nontrivial
in general.
--
Saumya Debray CS Department, University of Arizona, Tucson
internet: debray@arizona.edu
uucp: {allegra, cmcl2, ihnp4} !arizona!debray
Technical Details
Message-ID:
<5501@megaron.arizona.edu>Path:
arc1-ca.pugleaf.net!utzoo!attcan!uunet!husc6!bloom-beacon!tut.cis.ohio-state.edu!mailrus!ames!ncar!noao!arizona!debrayReferences:
<983@cresswell.quintus.UUCP>Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!utzoo!attcan!uunet!husc6!bloom-beacon!tut.cis.ohio-state.edu!mailrus!ames!ncar!noao!arizona!debray From: debray@arizona.edu (Saumya Debray) Newsgroups: comp.lang.prolog Subject: Re: Clause fusion Summary: optimization not all that obvious, in general Message-ID: <5501@megaron.arizona.edu> Date: 16 May 88 19:23:05 GMT References: <983@cresswell.quintus.UUCP> Organization: U of Arizona CS Dept, Tucson Lines: 165