Bug in clause/2?

I am using SWI-Prolog (threaded, 64 bits, version 10.0.2) on a Mac M3.
My single line program is:

   test(L) :- ( L = a ) . 

My query is

   ?- clause(test(L),Body).

I expected to get the same answer that I got in SWI Prolog 8.0.2, i.e.,

   Body = ( L = a ). 

But in version 10.0.2, I got the answer:

   L = a,
   Body = true

Do I have to call something different from clause/2 in order to get

   Body = ( L = a ) . 

This already came up some time ago.

The explanation from Jan was that unifications at the start of the body are moved to the head as an optimization.

Also, the manual warns explicitly about this possibility: https://www.swi-prolog.org/pldoc/man?predicate=clause/2

See https://github.com/SWI-Prolog/swipl-devel/issues/1485

Note that this does not hold for dynamic predicates:

:- dynamic test/1.

test(L) :- ( L = a ) .
?- clause(test(L),Body).
Body = (L=a).

Also note that clause/2 is not even supposed to work for static code. It does in SWI-Prolog, but the compiler takes the liberty to apply some semantic preserving rewrites to clauses to improve the performance.

My use case for a non-optimized clause/2 is the extension of a meta-interpreter to functional programming. I do not know how to keep my extension to functional programming simple without the non-optimized clause/2.

Here is my extended meta-interpreter:

i( true ) :- true .    
i( (Body1,Body2) ) :- i( Body1 ), i( Body2 ) . 
i( Goal ) :-  
    \+ Goal = true ,     
    \+ Goal = (_,_) ,          
    rw_final(Goal,Head) ,  % applying all functions to Goal yields a rule Head!
    clause(Head,Body) ,    
    i( Body ).        

rw_final( Old , New ) :- 
    subst( Old, Zw ) , ! ,        % single nested function call
    rw_final( Zw, New ).          % more function calls 
rw_final( Old , Old ) .


%  subst(+Old, -New ) - apply any substition to a subtree of Old to get New
subst( Old , Old ) :- var(Old) , ! , fail .        % abort for variables (no substitution)
subst( Old , Old ) :- atomic(Old) , ! , fail .     % abort for integers and atoms (no substitution)
subst( Old , New ) :- Old =.. [F,A|As] ,           % i.e. Old = F(A,...)
                      substl( [F,A|As], Ns ) ,     % inner function calls first
                      New =.. Ns .
subst( Old , New ) :- ( Old ==> New if B ) , B .   % apply functions and call the guard goal B

 %  substl( +L , -LwithSubst ) - apply subst to any element of L
substl( [A|As], [N|As] ) :- subst( A, N ) .
substl( [A|As], [A|Ns] ) :- substl( As, Ns ) .

Here are the operators needed to define functions:

:- op( 750 , xfx , ==> ) .           %  function operator ==>
:- op( 1180 , xfx , if ) .           %  function definition operator if

Here are the additions that allow to call a function and to return a function result with using the non-optimized clause/2 predicate.

X = Y ==> equal(X,Y)   if  true .    % needed to mask '=' in i(Goal)
equal(X,X) .   % needed to unify left-hand-side of '=' with function call result

Here are two example functions:

sqr(X) ==> X*X                    if  true.    % an example function

addlist( [ ] ) ==> 0              if true .    % a list as argument
addlist([X|L]) ==> X + addlist(L) if true .    % function using 2 clauses

Here is how I want to use the functions:

:- use_module(library(clpfd)).

go2(S,D,F) :- i( use_i(S,F) ) ,    % meta-interpreter yields: S=4*4 , F=3*3
              D #= F ,             % assignment computes D = 9
              ic(S,D) .            % Prolog goal checks ?- ic(4*4,9)

                                   % when called through meta-interpreter i(...)
  use_i(S,F) :-                    % I want to get use_i(4*4,3*3)
              S=sqr(4), F=sqr(3).  % but I get use_i(sqr(4),sqr(3))

  ic( 4*4 , 9 ) .                  % example Prolog fact


go5(L,R) :-   i(  use_5( L ) ) ,   % calling the meta-interpreter
              R #= L .             % evaluating term L and assigning result to R
 
                                   % when called through meta-interpreter i(...)
  use_5(L) :-                      % I want to get use_5( 3+0 )
       L = addlist([3]) .          % but I get use_5(addlist[3])  

I would really like to continue working with my meta-interpreter extension to functional programming as this also supports, e.g., substituting function calls by their result, nested functions, higher-order functions, using deterministic predicates as functions - and all this works well with older SWI-Prolog versions that do NOT optimize clause/2.
I do not know whether there is an easy workaround to solve the problems shown in the example above. If not, I would appreciate to have a chance to use a non-optimized clause/2 command
in an actual version of SWI-Prolog.

?- set_prolog_flag(optimise_unify, false).

Or use dynamic code. You should also make sure to disable optimization (-O, Prolog flag optimise) as this can make more modifications to static code (rewrite arithmetic, change calls that are always true to true (and then delete them), change always failing calls to fail and delete the remainder of the conjunction, simplify (if->then;else) if we know if is true or false, etc.

Thank you, Jan!
This works well.
I really appreciate your help!

Out of curiosity, can optimization be disabled for selected parts of a Prolog program?

The flag is effective when the clause is compiled, so you get change it locally in a file using a directive. I think we should add

:- push_prolog_flag(some_flag, some_value).
...
:- pop_prolog_flag(some_flag).

That would provide a simple scoping mechanism. Also quite useful as runtime construct. I think something like this is in use with Ciao?

Indeed! :slight_smile:

I could use this in clpBNR to manage the optimism_debug flag, since I use debug/3 and debugging/1 in optimized production code. I currently do this explicitly using a global var.

I could also use it in runtime code for managing some of the clpBNR specific flags.

You can use

:- set_prolog_flag(optimise_debug, true).

It still won’t affect the system libraries as the .qlf files are generated with optimization enabled. To get their debug/3 calls, use

swipl -Dsource ...

That causes SWI-Prolog to ignore the .qlf files and load the source instead.