Surprising result with equals dot dot

Using SWI-Prolog version 8.5.7

?- X = (2, 3), X =.. Y, length(Y, YLen), nth1(1, Y, Y1).
X =  (2,3),
Y = [,,2,3],
YLen = 3,
Y1 =  (,).

Is it correct for Y1 to ever be a tuple? Why default to such a confusing display of Y, which looks like a list of 4 elements?

1 Like

Maybe this explains things - with write_canonical(Y1) (using format/3, to get it into a Y1str as a string):

?- X = (2, 3), X =.. Y, length(Y, YLen), nth1(1, Y, Y1), format(string(Xstr), '~k', [X]), format(string(Y1str), '~k', [Y1]).
X =  (2, 3),
Y = [',', 2, 3],
YLen = 3,
Y1 =  (','),
Xstr = "','(2,3)",
Y1str = "','".

I’m also using 8.5.7, so it’s curious that your output is different.

Aah, this is what unintentionally adjusted my write settings to remove the single quotes (was in my ~/.config/swi-prolog/init.pl ) :

:- set_prolog_flag(answer_write_options,[max_depth(100)]).

I was expecting that, since I was not changing the other defaults, that they would keep their usual, sensible values :grinning:
I have changed it to:

:- set_prolog_flag(answer_write_options, [quoted(true), portray(true), max_depth(100), attributes(portray)]).

Does Y1, as a comma, make sense?

Yes. In this context, comma is an operator:

?- X =  (a, b).
X =  (a, b).

?- current_op(Precedence, Type, ',').
Precedence = 1000,
Type = xfy.
1 Like

I meant, why does Y1 involve a comma, when presumably it should be blank, since I specified (2, 3) instead of term_name_here(2, 3).

As opposed to the perfectly normal question and answer of:

?- X = normal_term(2, 3), X =.. Y, length(Y, YLen), nth1(1, Y, Y1).
X = normal_term(2,3),
Y = [normal_term,2,3],
YLen = 3,
Y1 = normal_term.

As you can see, there is no distinction between a term that’s specified with an operator and one that’s given in “prefix” form:

?- (a,b) = ','(a,b)
true

Perhaps this is clearer:

?- (a+b) = '+'(a,b).
true.

The only place where comma isn’t an operator is for separating the arguments in a term; everywhere else, it’s an operator. Compare:

?- write_canonical(p(a,b)).
p(a,b)

?- write_canonical(p((a,b))).
p(','(a,b))

?- write_canonical(p((a,b),c)).
p(','(a,b),c)

?- write_canonical(p((a,b),(c,d))).
p(','(a,b),','(c,d))
1 Like

I guess your question got answered already? The bottom line is, this:

(2, 3)

is not an “anonymous tuple” or anything of the sort. It is not the same as foo(2, 3) with just the “foo” missing.

foo(2, 3)

is a compound term with name “foo”, arity 2, and the two arguments are 2, 3.

(2, 3)

happens to parse as a compound term with name “,”, arity 2, and arguments 2, 3, because , is an operator. This is why this succeeds:

?- (2, 3) = ','(2, 3).
true.

Note that here the “','” is indeed an atom of length one.

This fails:

?- (2, 3) = ,(2, 3).
ERROR: Syntax error: Operator expected
ERROR: (2, 3) = 
ERROR: ** here **
ERROR: ,(2, 3) . 

Why does it fail and why do you think that it is “not allowed to redefine the comma”?

PS: I suspect or maybe faintly remember that with a different parser there might be a Prolog that allows you to redefine the comma.

1 Like

There is a so-called “comma list”, but its use is frowned upon as bad style; and a “regular” list is easier. Here’s an example (it works because the “,” operator is right-associative:
(a,b,c) = ','(a,','(b,c)):

% don't do this
sum_comma_list((X,Xs), Sum) :- !, % this cut is needed
    sum_comma_list(Xs, Sum0),
    Sum is X + Sum0.
sum_comma_list(X, X). % X \= (_,_)

?- sum_list([1,2,3], Sum).
Sum = 6.

Here is better style (notice that it doesn’t need a cut and the clauses can be in any order and it also allows a 0-length list, whereas a “comma list” must have at least one item):

sum_list([X|Xs], Sum) :-
    sum_list(Xs, Sum0),
    Sum is X + Sum0.
sum_list([], 0).

?- sum_comma_list((1,2,3), Sum).
Sum = 6.

My favorite demonstration about the pitfalls of comma lists (as seen somewhere on the internets):

?- [A, B] = [A, B, C].
false. % of course

?- (A, B) = (A, B, C).
B =  (B, C). % WAT?

Coming from a language that has “tuples” this second query should come as a nasty surprise. It also shows why comma lists are not tuples in the widely accepted meaning of the word “tuple”.

And of course [] (the empty list) is a thing while () (the empty tuple?) is not a thing, as you mention.

Both Python (hugely popular) and Haskell (hugely popular in some circles) have tuples that look like (a, b); and Prolog accepts this construct, and it seems to work. The end result is that you see a lot of Prolog code that uses those, some of it apparently coming from people teaching Prolog to other people, within the safe space of educational institutions.

4 Likes

Thank you, you are correct. However, no one who wants to use “tuples in Prolog” would start adding true as the last element of all their tuples, I thought. Maybe I am wrong.

Sometimes, when i have a goal/predicate as so pred(A, B) :- and i need to add an argument without adding causing pred/2 to become pred/3, I pass something like (X, Y) to bind with, say, B.

When placed before the original pred/2, these get found first.

Usually, its to try out things and to later rework the code without such contraption …

But, its a quick way to add an argument …

Do not do that. Use a pair, X-Y or just name your “tuple” somehow:

pred(A, B) -----> pred(A, X-Y)

or maybe

pred(A, B) -----> pred(A, x(X, Y))

I would call comma lists a “code smell” but the word “smell” is too nice :wink:

Yes, I use v(a, b, c) with the mnemonic “vector” or maybe “values”, if it is really so generic that I cannot come up with a short word that describes my a, b, c any better.

AFAIK, O’Keefe’s “The Craft of Prolog” already claims “never use a list for something of a fixed number of elements”. Instead, use a tuple as a compound term and try to give it a sensible name. For anonymous tuples with two arguments, use A-B which works nicely with library(pairs), keysort/2 and similar built-ins and libraries. I also tend to use v(…) if I see no need for a sensible name :slight_smile:

3 Likes

The form A-B tends to be used with the implied semantics Key-Value, and that can lead to subtle bugs if you assume the Keys are unique (I just fixed some code that made such a mistake), even though keysort/2 explicitly makes no assumption about uniqueness of keys.
I’ve also seen A-B used for difference lists (and sometimes A\B), with the mental image of subtracting B from the end of A.

As others have said, best to use a tuple; v(A,B,C) is better than A-B-C (and also slightly more efficient), and even better if you can use something more meaningful than “v” for the functor name.

The “minus” as in A-B is only good for a pair, it doesn’t extend to A-B-C. That suffers the exact same problem as “comma lists”, BTW, just swapped:

?- A-B = A-B-C.
A = A-C,
B = C.

The “clean” way to extend - from a pair to a triple, quadriple etc would be to do the exact same thing you’d do with the comma:

','(a, b), ','(a, b, c), ','(a, b, c, d)
-(a, b), -(a, b, c), -(a, b, c, d)

what I now learned and find curious is that others also use v(a, b, ...) for this.

There is a lot of interesting lessons to be learned from R. IIRC the c() in R is a function that combines or maybe concatenates its arguments to a one-dimensional vector (and it takes as many arguments as you give it…).

It seems that ECLiPSe has a very similar interface to arrays as R. In R at least you can at run time change the dimensions of an array (efficiently) by assigning a vector of dimensions to your array.