1

If for example, I have a Prolog predicate like a(A, B).

Is it possible to collect, given a value of A, is it possible to collect all values of B that succeeds the predicate a, into a list, without using built in predicates such as bagof/3 or findall/3.

false
  • 10,264
  • 13
  • 101
  • 209
Eason51
  • 17
  • 4

2 Answers2

3

You have two obvious options (obvious to me; it seems there is more). One is to indeed use the database to save the state. This has at least one pitfall: depending on the name you decide to use for the temporary state, you might destroy some other state your program is keeping. This is the same old "global state"/"global variable" problem that all languages suffer from.

The other option would be to use a "local variable" and non-backtracking assignment to it to keep the temporary state. This is most probably going to be implementation dependent. For starters, you can look at nb_setarg/3 for SWI-Prolog.

However, both solutions are silly, given that you have findall, bagof, setof. You must motivate the need for something else to replace those. Just saying "is it possible" is not good enough since it is possible, but completely unnecessary, unless you know something else that you aren't telling us.

TA_intern
  • 2,222
  • 4
  • 12
  • And, of course, there is a third option which is to use if-then-else and `copy_term/2`. This would give you only a setof-like functionality, though. – false Dec 07 '21 at 11:09
  • @false This is I guess similar to keeping a "local variable", just with a different mechanism? EDIT Actually I don't understand how this would work. Could you explain with a bit of detail? – TA_intern Dec 07 '21 at 11:20
  • Also other built-ins and control constructs would be needed like `(',')/2`, `call/1`, `subsumes_term/2`. – false Dec 07 '21 at 11:36
  • 2
    @false I would very much appreciate an answer that at least outlines what you mean – TA_intern Dec 07 '21 at 11:38
  • It seems you do not appreciate an answer like the [answer](https://stackoverflow.com/a/70262772/772868) by @IsabelleNewbie. – false Dec 08 '21 at 10:45
  • @false I have been trying to digest it. I need to work for money, sadly, and it sometimes prevents me from concentrating on the important stuff. – TA_intern Dec 08 '21 at 14:22
  • Yesterday, there was one dv only – false Dec 09 '21 at 20:45
2

Here's a sketch of a stupid setof that uses other builtins, though not assert, and not exactly the ones listed by @false in a comment.

We'll use a list accumulator to collect solutions:

stupid_setof(Template, Goal, Set) :-
    stupid_setof(Template, Goal, [], Set).

There are two cases to consider: Either the Goal can enumerate a solution we have not seen so far, or the only ones it can enumerate are already in our accumulator.

First, the case where there are no solutions we haven't seen. In this case we're done.

stupid_setof(Template, Goal, SolutionsSeen, Set) :-
    \+ (  call(Goal),
          \+ member(Template, SolutionsSeen) ),
    !,    
    sort(SolutionsSeen, Set).

Now for the stupid part. Consider:

foo(a).
foo(b).
foo(c).

?- SolutionsSeen = [], foo(X), \+ member(X, SolutionsSeen), !.
SolutionsSeen = [],
X = a.

?- SolutionsSeen = [a], foo(X), \+ member(X, SolutionsSeen), !.
SolutionsSeen = [a],
X = b.

?- SolutionsSeen = [a, b], foo(X), \+ member(X, SolutionsSeen), !.
SolutionsSeen = [a, b],
X = c.

?- SolutionsSeen = [a, b, c], foo(X), \+ member(X, SolutionsSeen), !.
false.

So given a list of solutions we've seen before, we can force Goal to backtrack until it gives us one that we haven't seen before. Note that these queries are independent: In each one we have a completely fresh copy of the foo(X) goal that starts enumerating from a.

We can do the same thing programmatically by copying the original goal before calling it, forcing it to start a fresh enumeration from a fresh instance of the Goal. If this finds a new solution, we can add it to our solutions, then repeat with another fresh copy of the goal, forcing it to enumerate yet another new solution, and so on:

stupid_setof(Template, Goal, SolutionsSeen, Set) :-
    copy_term(Goal-Template, GoalInstance-Solution),
    call(GoalInstance),
    \+ member(Solution, SolutionsSeen),
    !, 
    stupid_setof(Template, Goal, [Solution | SolutionsSeen], Set).

If Goal has N answers, this will enumerate on the order of N**2 of them and do corresponding linear searches in the solutions list. It will also perform any side effects that Goal has multiple times.

But it "works":

?- stupid_setof(X, foo(X), Xs).
Xs = [a, b, c].

And, despite all of its stupidity, this is still less stupid than the standard setof/3 if Goal has no solutions:

:- dynamic bar/1.  % no clauses

?- setof(X, bar(X), Set).
false.

?- stupid_setof(X, bar(X), Set).
Set = [].
Isabelle Newbie
  • 9,258
  • 1
  • 20
  • 32
  • Both the `!/0` and `(\+)/1` can be also encoded with `(;)/2` if-then-else. Further, a `subsumes_term/2` together with `member/2` makes it a bit better. – false Dec 07 '21 at 16:46
  • 1
    As for the suggested stupidity of `setof/3`, you are certainly aware that this admits several solutions and thus is a bit more monotonic than your solution. – false Dec 07 '21 at 16:50
  • I'm not aware of the latter point, could you explain? – Isabelle Newbie Dec 07 '21 at 17:12
  • 3
    Consider `G_0 = setof(B,member(A-B,[a-1,a-2,b-1]),As).` As long as `B` is not affected, this behaves like a pure relation for `A`. So `A = a, G_0`, `A = b, G_0`, `A = c, G_0` all fit in. That's the reason why there is never a solution with `As = []`. – false Dec 07 '21 at 17:35
  • Still there are cases, like [BOM](https://en.wikipedia.org/wiki/Bill_of_materials) queries, where clearly the empty list would be so much better. – false Dec 07 '21 at 17:41
  • Thanks for the example. I guess I don't see what you're getting at and what it has to do with monotonicity. `As = []` would fit in very nicely. But, well, I'll just live with it and keep complaining. – Isabelle Newbie Dec 07 '21 at 22:48
  • What should the answers be for `G_0`? `A = a, As = [1,2] ; A = b, As = [1]`. But if you are into `[]` shouldn't `A = c, As = []` be an answer, too? And what about the other constants and terms that are all `dif(A,a), dif(A,b)`? – false Dec 08 '21 at 05:55
  • Yes. In a formal set theory, the formula `A = c & As = {}` is equivalent to the formula `A = c & As = { B in Universe : false }`. I would expect `setof/3` to behave as much as possible like set comprehensions in a formal set theory like ZF. I honestly don't understand why this is controversial. I do understand why it can't be **changed**. – Isabelle Newbie Dec 08 '21 at 12:09
  • 1
    Again, I'm OK with accepting this historical quirk and moving on. If you feel like you want to write a long-form explanation of your point of view (not just brief comments), let me know. Then I'll post a proper question so you can post a proper answer. – Isabelle Newbie Dec 08 '21 at 12:11
  • If you want `A = c, As = []`, then you should also want all the other terms as answers which are infinitely many. That is not very attractive. Almost every such query would now not terminate. – false Dec 08 '21 at 13:59
  • You're becoming very vague with "such query" and "all the other terms". `setof0(T, G, S) :- (setof(T, G, S) -> true ; S = []).` has the same termination behavior as `setof/3`. The query `setof0(B, false, As).` gives `As = []`, and if you add other conjuncts to the query like `A = c` or `dif(A, a), dif(A, b)`, you will get those conjuncts answered back. End of thread as far as I'm concerned, thanks. – Isabelle Newbie Dec 08 '21 at 15:10
  • 1
    `setof0(t, true, [])` succeeds? – false Dec 08 '21 at 16:51