Careful with setof/3

Is this ok?

/* Welcome to SWI-Prolog (threaded, 64 bits, version 8.5.14) */

?- findall(t, (L=2;L=1), L).
L = [t, t].

?- setof(t, (L=2;L=1), L).

By way of Tau Prolog, which has yet another behaviour.

You need the “there exists” operator (“^”);

?- setof(t, L^(L=2;L=1), L).
L = [t].

This is one reason I don’t put any variables in the goal that aren’t also in the template.

1 Like

It’s probably a bad idea to use the same variable within the goal and for the results (unless the variable is existentially qualified or in the template). When I change the results variable, I get this:

?- setof(t, (L=2;L=1), X).
L = 1,
X = [t] ;
L = 2,
X = [t].
?- setof(L, (L=2;L=1), L).
L = [1, 2].

I wonder what really happens, that it fails. I get:

?- setof(t, (L=2;L=1), R).
L = 1,
R = [t] ;
L = 2,
R = [t].

So I guess the unification of 1 with [t] and the unification of 2
with [t] fails. Making the thing setof(t, (L=2;L=1), L) fail?
But this here succeeds:

?- setof(t, (L=[t];L=[t]), L).
L = [t].

But how can we assure that setof/3 has not some steadfastness issues?

I am not sure on what’s point here, but I ran posted queries and a few similar for curiosity to see what happen. As far as programming of mine concerned, behavior seems reasonable, though “steadfastness” is new for me, only remembering it was discussed somewhere.

( version 8.5.15-54-g38e7e2564)

?- setof(t, L=1;L=2, S).
L = 1,
S = [t] .

?- bagof(t, L=1;L=2, S).
L = 1,
S = [t] .

?- findall(t, L=1;L=2, S).
S = [t, t].

?- findall(t, L=[t];L=[t], S).
S = [t, t].

?- setof(t, L=1;M=2, S).
M = 2,
S = [t] .

ans(S):- setof(t, L=1;M=2, S).
?- ans(S).
S = [t] ;
S = [t].

ans(S, L):- setof(t, L=1;L=2, S).
?- ans(S, L).
S = [t],
L = 1 ;
S = [t],
L = 2.

For example sort/2 is not anymore steadfast in Scryer Prolog.
A predicate is steadfast in some output argument Y if:

p(X,Y), Y = c.

and this here, do the same:


Now I get in Scryer Prolog:

?- sort([1,2,3], X), X = a.

?- sort([1,2,3], a).

But in SWI-Prolog:

?- sort([1,2,3], X), X = a.

?- sort([1,2,3], a).

I wonder whether some steadfastness violation can happen
with findall/3, setof/3 or bagof/3 in SWI-Prolog.

Thanks for explaining steadfastness, which seems important for compiler to optimize codes for eliminating redundant unification, for example, as in your sample.

The Tau-Prolog behaviour is:

?- setof(t,(L=2;L=1),L).
uncaught exception: error(type_error(list,1),sort/2), unexpected.

Which is maybe an artefact of the different behaviour of sort/2.

But I am still not 100% sure whether something similar might happen in SWI-Prolog?

As far as I’m concerned this should all be remedied using a linter and/or type checker. May predicates misbehave if you use the same variable for input and output. I had a long discussion long time ago about length(L,L). A type checker should tell you integer and list are incompatible, so there are no solutions. Output arguments in SWI-Prolog are never type-checked at runtime, despite the ISO standard demanding this for some predicates. I consider that a bad idea, in particular for lists as it requires processing the whole list. In addition I’d like the claim that output arguments behave as p(X), X = c.

Hi @jan, is the list of those predicates where SWI diverges from ISO documented? Ciao Prolog requires a precise definition of “output” in order to do correct static analysis and probably our assumptions for many predicates also diverges from ISO.

If there are good enough reasons to push a change into the standard, it would be worth it. In the worst case, even if it is never accepted, users of all Prolog systems may appreciate if there is a common text describing non-standard changes to ISO. Hopefully every Prolog dialect could be described as a delta w.r.t. ISO.

We could do ‘cross citations’ between Ciao and SWI documentation, but it would be a mess. I still believe that some common forum for implementors a-la “Bytecode Alliance” to share and review specifications may be positive, if there is room for both industrial and academic people (I think there is).

Some systems may decide to be “broad and generalist” and support many dialects (like SWI), others may decide to be more focused (e.g., in Ciao we traditionally have prioritized decisions that makes static analysis feasible, even though they break the compatibility with other traditional Prolog code), but at least there would be a way to understand what each system is doing and why.

This may also solve some of some ugly aspects (from my side) of the current Prolog ecosystem: academic systems look for solutions, which takes a lot of time, but do not always have the resources to push into industrial solutions. All good ideas of academic systems are adopted by industrial ones, which is really awesome, but unfortunately the origin of those ideas is often lost.

We’d not need to invent anything, just follow what does and try to involve as much implementors as possible (I’m aware that implementors of other Prolog systems also like PEPs). Using something like markdown would not be harder than typing messages here or in our documentation systems. Rather than saying “Ciao does feature X like SWI” we could say “Ciao implements PEP37” and point to a common place that describes (forever and for every other system) what PEP37 means. PEP37 can be open for everyone to contribute. It could cite research papers and research papers could cite them (rather than vague statements like “as implemented in Ciao or SWI…”). I could write in my CV: I designed PEP37 and showed that it worked, it was adopted by system X,Y,Z… It could also be possible to have completely incompatible proposals, each system is free to implement whatever is better for them. Some PEP may be trivial and others hard research problems.

If anyone likes the idea I can help implementing it and contacting other systems.

Its funny that Ulrich Neumerkel raised the issue, slightly
implying he wants not a type error? On the other hand his
latest length/2 test cases, are like 75% type error and

domain error tests of some output argument, which in
my opinion can be easily dropped from a certain
interpretation of the ISO core standard. It only causes

slowdown of the Prolog system. Among the Prolog systems
that pass these nonsense tests are also mostly Prolog systems
that are not the fastest. See for yourself:

Edit 14.08.2022
In length/2 its easy to nevertheless support these errors,
since one can use a var/1 test in the second argument instead
of a ground/1 test in the first argument, when deciding the mode.

In as far I can offer this behaviour:

?- length([a,b,c],x).
error(type_error(integer, x), [user:1])
?- length([a,b,c],-1).
?- length(X,-1).

Isn’t the fail nice?
The fail is not on the agenda of Ulrich Neumerkel.
Also tests such as:

?- length([a|b], X).
ERROR: Type error: `list' expected, found `[a|b]' (a compound)

Are totally missing in the assesment page by Ulrich Neumerkel.
So what is the coverage?

Not in any comprehensive way. Most of the ISO incompatibilities are mentioned in the documentation, but surely not all of them. Notably SWI-Prolog typically does not care about the exact exception for exceptions that are normally only caused by a broken program. For example, the exceptions caused by e.g., open/4 may well be caught and handled by an application. Except for resource errors, exceptions raised by e.g., length/2 are caused by a buggy program and the only thing that matters is to give the programmer as good as possible feedback about what happened.

PEPs could be a good way to move forward. Currently our biggest problem are all the built-ins and libraries provided by the various systems that are not covered by ISO (despite all corner cases it seems pretty easy to write portable Prolog code as long as you do not need anything outside the ISO core standard). For that, I think the HTML/CSS/JavaScript docs that specify which browsers implement what since which version with additional notes on differences looks better to me. If we could bootstrap this by automatic conversion of existing documentation it might work.

Agree. Let us start by trying to make predicates compatible when called with valid arguments. IMO it is up to Prolog systems to help the user to debug programs. That could can be static analysis, runtime tools, etc. If I recall correctly, call((fail, 42)) must raise a type error on 42 not being callable according to ISO. That IMO makes no sense. It is fine if a system does this. It is also fine if it refuses to compile a clause holding this code and it is also fine if it silently fails.