Careful with dynamic/1

Looks like there is a little side effect by dynamic/1 directive, which
works only downstream in the source code, but not upstream.

I was currious whether I can turn a static predicate into a
dynamic predicate, this is warned in GNU Prolog:

?- [user].
foo.

:- dynamic(foo/0).
user:3: warning: directive occurs after definition of foo/0 - directive ignored

But it works in SWI-Prolog, both are accepted (File dynoder.pl):

bar(X) :- X = baz.
:- dynamic bar/1.

:- dynamic bar2/1.
bar2(X) :- X = baz.

But because of the particular order of arrival of the clauses,
in case of bar/1 we get a different listing, the (=)/2 moved to the head,
whereas in case of bar2/1, the (=)/2 is not moved to the head.

?- listing(bar/1).
:- dynamic bar/1.

bar(baz).

?- listing(bar2/1).
:- dynamic bar2/1.

bar2(A) :-
    A=baz.

Bug or feature?

The feature that allows static predicates to be converted into dynamic ones at runtime is surely SWI-Prolog specific. I do not see much reason to disallow it though. It is IMO mostly up to the source code checkers to warn about this. Note that SWI-Prolog also allows calling dynamic/1 as a predicate. It is a bit odd that other systems do not allow for that as any ISO compliant system can create a dynamic predicate using the sequence asserta/1, retractall/1.

1 Like

Is there some style check flag, to enable such a check.
I was using dynamic/1 as a directive inside source code:

I have attached the test file:
dynorder.pl (80 Bytes)

Edit 04.04.2022:
The style check seems to be very common.
In case of ECLiPSe Prolog I find this behaviour:

procedure already defined in dynamic bar / 1 in module eclipse
ERROR: dynorder.pl:2:

That seems to be an error. Most systems probably cannot make the change safely. I see little reason for a style check. As it, it is 99% a portability issue that you will find when loading the code into another Prolog implementation. In theory the iso flag should to that. It is not very realistic to implement that in a comprehensive way.

SWI-Prolog can also not make the change safely.
You see this when you load dynorder.pl and then do listing/1:

In case of bar/1 we get a different listing, the (=)/2 moved to the head,
whereas in case of bar2/1, the (=)/2 is not moved to the head.
Thats basically the reason why I posted the problem. The problem

did not exist in old SWI-Prolog that didn’t do the head optimization.
So its an inner problem caused from optimization, not an outer
problem caused from some ISO core standard compatibility.

I didn’t lookup what the ISO core standard would prescribe.

Edit 15.04.2022:
Most likely the ISO core standard cannot shed a lot of light since it does
not address optimizations. The new feature of SWI-Prolog that causes the
discrepancy betweeen the two predicates bar/1 and bar2/1 is this here:

2.18.3 Indexing for body code
https://www.swi-prolog.org/pldoc/man?section=indexbody

If I am not totally mistaken, it also affects what clause/2 gives, and what
an application expects that used the dynamic directive. But I didn’t experiment
yet what clause/2 would do. For other Prolog systems like GNU-Prolog

and ECLiPSe Prolog there is nothing to experiment with, since they do not
promote bar/1 to dynamic. So clause/2 should give an error.

Are you suggesting that clause/2 should always return exactly what was used to create the clause, and can’t return something that’s semantically equivalent? This implies keeping extra information around for something that’s rarely needed.

(I think that in some systems, clause/2 only works if the predicate is dynamic - I don’t have a copy of the ISO standard, I don’t know if this is ISO-conforming or not.)

ISO dictates indeed that clause/2 only works on dynamic predicates and returns exactly the same clause. SWI-Prolog doesn’t comply here. It allows clause/2 in static code but only promises to return a semantically equivalent clause. If the target predicate is dynamic some of the optimizations are disabled, but there is in general still no promise to get the exactly same clause. For example:

swipl -O
...

?- dynamic(p/1).
true.

?- assert((p(X) :- Y is X-1, writeln(Y))).
true.

?- listing(p/1).
:- dynamic p/1.

p(A) :-
    B is A+ -1,
    writeln(B).

true.

This is because SWI-Prolog has a VM instruction to add a small integer to a variable and assign it to what is known to be an unbound variable. There however is no subtract equivalent, so it adds its negative equivalent. This notably speeds up counters :slight_smile:

I see little reason to change this. I do see this is not documented with clause/2 though.

You copied this out of context. I was refering to GNU-Prolog
and ECLiPSe Prolog, not to SWI-Prolog.

I now did some testing, with exiting and entering [user].
ECLiPSe Prolog throws an error, and doesn’t promote:

[eclipse 1]: [user].
source_processor.eco loaded in 0.00 seconds
hash.eco   loaded in 0.00 seconds
compiler_common.eco loaded in 0.00 seconds
compiler_normalise.eco loaded in 0.00 seconds
compiler_map.eco loaded in 0.00 seconds
compiler_analysis.eco loaded in 0.00 seconds
compiler_peephole.eco loaded in 0.00 seconds
compiler_codegen.eco loaded in 0.02 seconds
compiler_varclass.eco loaded in 0.02 seconds
compiler_indexing.eco loaded in 0.00 seconds
compiler_regassign.eco loaded in 0.00 seconds
asm.eco    loaded in 0.03 seconds
module_options.eco loaded in 0.00 seconds
ecl_compiler.eco loaded in 0.08 seconds
 foo.
 ^Z
tty        compiled 16 bytes in 0.00 seconds

Yes (0.08s cpu)
[eclipse 2]: [user].
 :- dynamic(foo/0).
procedure already defined in dynamic foo / 0 in module eclipse
ERROR: tty stream input:2:
  Query exited (abort): :- dynamic foo / 0
 ^Z
Error(s) occurred while compiling user
Aborting execution ...
Abort
[eclipse 3]: foo.

Yes (0.00s cpu)
[eclipse 4]: clause(foo,_).
procedure not dynamic in clause(foo, _65) in module eclipse
Abort

GNU-Prolog doesn’t throw an error anymore, when there is exiting
and entering [user]. This is possibly in line with the clear predicate
on reconsult semantic, as GNU Prolog seems to clear the predicate:

?- [user].
compiling user for byte code...
foo.

user compiled, 1 lines read - 150 bytes written, 3251 ms

yes
| ?- [user].
compiling user for byte code...
:- dynamic(foo/0).

user compiled, 1 lines read - 120 bytes written, 11619 ms

(250 ms) yes
| ?- foo.

no

Edit 06.04.2022:
I also tried SWI-Prolog, it has neither the ECLiPSe nor the
GNU Prolog semantic when I also test exiting and entering [user]:

?- [user].
|: foo.
|: 
% user://1 compiled 0.00 sec, 1 clauses
true.

?- [user].
|: :- dynamic(foo/0).
|: 
% user://2 compiled 0.00 sec, 0 clauses
true.

?- foo.
true.

Usually SWI-Prolog has also a clear predicate on reconsult
semantic. But unlike GNU Prolog, this clear predicate on
reconsult semantic, does not apply to the dynamic directive,

as my previous post demonstrated. Here you see the clear
predicate on reconsult semantic in action, to demonstrate
what the clear predicate on reconsult semantic means.

SWI-Prolog shows a warning, indicating the predicate gets
cleared, before it gets reconsulted:

?- [user].
|: p(a).
|: p(b).
|: p(c).
|: 
% user://3 compiled 0.00 sec, 3 clauses
true.

?- p(X), write(X), nl, fail; true.
a
b
c
true.

?- [user].
|: p(x).

Warning: user://4:37:
Warning:    Redefined static procedure p/1
Warning:    Previously defined at user://3:23
|: p(y).
|: 
% user://4 compiled 0.00 sec, 2 clauses
true.

?- p(X), write(X), nl, fail; true.
x
y
true.

Edit 06.14.2022:
This actually leads to a subtle bug when reconsulting. The bug
is seen here. Since the clear is only done when some clause arrives
and not already during the directive, the clear also removes the

directive. So you can basically not reconsult and change from static
into dynamic. Whereby this would work correctly in GNU Prolog:

?- [user].
|: foo.
|: 
% user://1 compiled 0.02 sec, 1 clauses
true.

?- [user].
|: :- dynamic(foo/0).
|: foo.

Warning: user://2:15:
Warning:    Redefined static procedure foo/0
Warning:    Previously defined at user://1:8
|: 
% user://2 compiled 0.00 sec, 1 clauses
true.

?- predicate_property(foo, X).
...
X = static ;
...

The bug is that the predicate is static and not dynamic.