2

In SWI Prolog:

Exact clause duplicates are allowed:

a(1).
a(1).
?- a(X).
X = 1 ;
X = 1.

or even:

c :- format("Hello from first c!").
c :- format("Hello from second c!").
Hello from first c!
true ;
Hello from second c!
true.

More generally, so are clauses with identical fully ground heads but differing bodies:

b(1) :- format("Hello from first b!").
b(1) :- format("Hello from second b!").
?- b(1).
Hello from first b!
true ;
Hello from second b!
true.

Clauses with identical non-ground head feel somewhat more reasonable:

p(X) :- format("Yup, this is p(~w)",X).
p(X) :- format("Yup, this is also p(~w)",X).
p(X) :- format("You think you can get rid of good old p(~w) just like that?",X).
?- p('homer simpson').
Yup, this is p(homer simpson)
true ;
Yup, this is also p(homer simpson)
true ;
You think you can get rid of good old p(homer simpson) just like that?
true.
?- p(X).
Yup, this is p(_4782)
true ;
Yup, this is also p(_4782)
true ;
You think you can get rid of good old p(_4782) just like that?
true.

This covers the reasonable case of clauses with a guarded body:

p(X) :- X < 0,   format("It's less than 0: ~w", X).
p(X) :- X =:= 0, format("It's exactly 0: ~w", X).
p(X) :- X > 0,   format("It's larger than 0: ~w", X).

On second thoughts ... we already encounter the ultimate case in the built-in repeat:

?- repeat.
true ;
true ;
true ;
true ; 
… 

Or can construct an intermediate case easily:

h :- member(_,[1,2,3]).
?- h.
true ;
true ;
true.

Somehow textbooks gloss over the fact that predicates have additional semantics: they can not only be false or true for any given ground arguments, but they can actually be true(n) - "true n times, n ≥ 0" .

From a theory standpoint, this is dubious, at least for vanilla classical logic.

On the other hand, it is useful from a computational standpoint for:

  • Side-effects (rather for output than for input, and rather marginally).
  • Control of the computation (i.e. repeat).

Are there any other uses?

I really feel the compiler should flag variable-less cases like a/1, c/0, b/1 above as errors (easy to detect), and even repeat/0 should probably have a value: repeat(Count). Anything which redoes successfully should NOT redo successfully in the same context on exactly the same ground arguments. It has the same squishy feeling of irrelevancy as a(X) :- b(Y). b(Z). Brrr!.

David Tonhofer
  • 14,559
  • 5
  • 55
  • 51
  • 1
    Suggestion for a further question: *What uses do redundant solutions/answer have?* – false Feb 18 '20 at 17:02
  • Here's another suggestion: "*Given some pure goal `G`. What good is comparing the answer sequence of `?- G.` and `?- G, G.` ?*" – repeat Feb 18 '20 at 18:46
  • 1
    Exactry! Think of `p(a+a). p(X+X).` – false Feb 18 '20 at 19:31
  • 1
    *Brrr!* Also this rule has its legitimacy during diagnosis (d-bugging) in particular as the result of a generalization. – false Feb 18 '20 at 19:42

3 Answers3

3

Yes, for diagnostic purposes in pure programs. By duplicating a clause you can answer the question how often the clause contributes to solutions.

That is, you count the answers/solutions you get from a query and compare these to the same program plus the duplicated clause. If the number of (redundant) solutions now increases you know that this clause contributes. The ld factor tells you how often.

Note that a tracer cannot tell you this as easily.

false
  • 10,264
  • 13
  • 101
  • 209
  • Nifty! I wish there was a collection of "programming devices" like this one... – repeat Feb 18 '20 at 18:51
  • I see. Industry developers will ride into the sunset before they ever hear about this trick. Also, won't tabling be an impediment? I think it will. – David Tonhofer Feb 18 '20 at 20:58
  • Tabling doesn't. But constraints do. Not sure what your comment about industry developer means. – false Feb 18 '20 at 21:27
1

Some observations in the particular case of duplicated clauses.

If the repeated clauses for predicate a/1 are found in a Logtalk object or in a Prolog module that can be compiled as an object, the Logtalk linter can warn you of duplicated clauses. For example, given the following module:

:- module(duplicates, []).

a(1).
a(1).

We get:

?- set_logtalk_flag(duplicated_clauses, warning).
true.

?- {duplicates}.
*     Duplicated clause: a(1)
*       first found at or above line 3
*       while compiling object duplicates
*       in file /Users/pmoura/duplicates.lgt at or above line 4
*     
% [ /Users/pmoura/duplicates.lgt loaded ]
% 1 compilation warning
true.

This particular lint warning is usually turned off by default due to its computational cost. If you load the tutor tool prior to the compilation of the module, you will get instead:

?- {tutor(loader)}.
...
% (0 warnings)
true.

?- set_logtalk_flag(duplicated_clauses, warning).
true.

?- {duplicates}.
*     Duplicated clause: a(1)
*       first found at or above line 3
*       while compiling object duplicates
*       in file /Users/pmoura/Desktop/duplicates.lgt at or above line 4
*     Duplicated clauses are usually a source code editing error and can
*     result in spurious choice-points, degrading performance. Delete or
*     correct the duplicated clause to fix this warning.
*     
% [ /Users/pmoura/Desktop/duplicates.lgt loaded ]
% 1 compilation warning
true.

The warning is useful and proved itself worth in non-trivial codebases.

Paulo Moura
  • 18,373
  • 3
  • 23
  • 33
1

@false mentions in his answer using duplicated clauses to "answer the question how often the clause contributes to solutions." He also mentions "that a tracer cannot tell you this as easily".

A better solution to this use case is to use a ports profiler tool as found in e.g. ECLiPSe and Logtalk. No need to duplicate clauses to gather that information.

As an example, consider the following pure program, saved in a pure.pl file:

a :- b, c.

b.
b :- d.

c.

d.

Let's include this code in an object. We can define it in a source file:

:- object(pure).

    :- set_logtalk_flag(debug, on).

    :- public(a/0).
    :- include('pure.pl').

:- end_object.

Alternatively, we can create pure as a dynamic object:

?- create_object(pure, [],  [set_logtalk_flag(debug,on), public(a/0), include('pure.pl')], []).
true.

Either way, after loading or creating the pure object and loading the ports_profiler tool, we can e.g. query all solutions for the a/0 predicate and then print the profiling data:

?- {ports_profiler(loader)}.
...
% (0 warnings)
true.

?- pure::a.
true ;
true.

?- ports_profiler::data.
-------------------------------------------------------------------
Entity  Predicate    Fact  Rule  Call  Exit *Exit  Fail  Redo Error
-------------------------------------------------------------------
pure    a/0             0     1     1     1     1     0     1     0
pure    b/0             1     1     1     1     1     0     1     0
pure    c/0             2     0     2     2     0     0     0     0
pure    d/0             1     0     1     1     0     0     0     0
-------------------------------------------------------------------
true.

The table displays port crossing information for all predicates used in the query to the a/0 predicate. For example, that the predicate c/0 as called twice (for the two solutions to a/0) and exited deterministically. On the other hand, the predicate b/0 was called once by succeeded twice (on of them non-deterministically) due to backtracking.

For details on the portable port_profiler tool and a discussion on the insights that it can provide, see:

https://logtalk.org/manuals/devtools/ports_profiler.html

Paulo Moura
  • 18,373
  • 3
  • 23
  • 33
  • You are doing something only remotely related. Take `a :- b(X), X = 2. b(1).` How often is `b/1` contributing to a solution of `?- a.`? None, whereas your tool will show some numbers. – false Feb 18 '20 at 19:35