?- append(X, Y, Y).
X = [] ;
X = [_A],
Y = [_A|Y] ;
X = [_A, _B],
Y = [_A, _B|Y] ;
Can be fixed with:
append(L, R, A) :-
( R == A
-> L = [],
% Loop through lengths of R
append_(R, L, A)
; L == A
-> R = [],
append_(L, R, A)
; append_(L, R, A)
).
append_([], L, L).
append_([H|T], L, [H|R]) :-
append_(T, L, R).
… to instead produce:
?- append(X, Y, Y).
X = Y, Y = [] ;
X = [],
Y = [_] ;
X = [],
Y = [_, _] ;
Is this simply a bug with an easy fix, or does the cyclic term have a deliberate use?
Why is creating a cyclic term not the right thing to do here though?
The way I read append(X, Y, Y) declaratively is “Y is the list whose beginning is X and tail is Y”. An immediate consequence is that Y is cyclic unless X is empty (which is what we get with your suggested implementation).
I think this situation is too “normal” for (logic) programming to “fix”.
Not necessarily as it depends on the programmer’s intent. Some data structures may need a reference to themselves (but in most cases, they probably don’t).
So the question is: which way should have been the default? I guess the early Prolog programmers decided that cyclic terms should be possible by default.
There is the “occurs_check” flag, is that a kind of documentation? If you set it to true the append example leads to non-termination, but if set to error it just throws:
?- append(X, Y, Y).
X = [] ;
X = [_A],
Y = [_A|Y] ;
X = [_A, _B],
Y = [_A, _B|Y] . % and so on
?- set_prolog_flag(occurs_check, true).
true.
?- append(X, Y, Y).
X = [] ; % then non-termination
?- set_prolog_flag(occurs_check, error).
true.
?- append(X, Y, Y).
X = [] ;
ERROR: Cannot unify _1416 with [_1422|_1416]: would create an infinite tree
ERROR: In:
ERROR: [11] lists:append([],[_1482|_1484],_1478)
ERROR: [9] toplevel_call('<garbage_collected>') at /Applications/SWI-Prolog.app/Contents/swipl/boot/toplevel.pl:1173
ERROR:
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.
Exception: (11) lists:append(_60, [_58|_66], _66) ? abort
% Execution Aborted
Yes, I know it as changable flag. However, the flag seems set as false as Default.
% swipl
Welcome to SWI-Prolog (threaded, 64 bits, version 9.1.11-5-gddaf93732)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
?- X=[a,X].
X = [a, X].
?- current_prolog_flag(occurs_check, F).
F = false.
I have checked, thanks. It seems enough to to say that cyclic terms are default for SWI-Prolog, though “error” value there is new to me, which looks for me too much kind for the prologer. I would rather insert “cyclic_term/1” for checking in stead.
I would suggest you use the occurs_check flag if you don’t want cyclic terms. It would be a huge change to work through all the textbook predicates, it will break things for the outliers, and finally, you can just flip a switch and achieve the same effect globally.
Is there a particular reason why the flag solution doesn’t work for you?
I kind of like the fact that it comes with the, probably unexpected, cyclic solution As cyclic terms are part of the normal Prolog term space, it is simply correct and (thus) I see no reason to make append/3 ugly.
About the flag, it has this intend:
false (default) traditional Prolog mode. Note that SWI-Prolog supports cyclic terms nearly everywhere where it makes sense and built-ins that do nor support them should raise an exception. Pure Prolog predicates tend to loop if they are not prepared to handle cyclic terms. They can be interrupted though.
true classical Herbrand universe, causing goals that try to create a cyclic term to fail. Can be really slow though. In practice, it works well for most programs, resulting in only a few percent slowdown. If I recall correctly, the worst case is exponential though. Note that cyclic terms are useful data structures and this mode rules them out. Some programs stop working.
error is meant to help debugging programs that accidentally create a cyclic term. It is by default enabled for the SWISH student profile. This profile also reduces the stack limit, such that buggy programs bail out quickly.