So, how to declaratively read
remove([X|Xs], [X|Ys]) :-
Xs = [_,_|_],
remove(Xs,Ys).
Most important is that you first realize what the :-
actually means.
Head :-
Body.
It means: Whenever Body holds, we can conclude that also Head holds. Note the rather unintuitive direction of the arrow. It goes right-to-left. And not left-to-right, as often written informally when you conclude something. However, the error points into the direction of what we get "out of it".
To better see this, you can enter Body as a query!
?- Xs = [_,_|_], remove(Xs,Ys).
Xs = [A, B], Ys = [B]
; Xs = [A, B, C], Ys = [A, C]
; ... .
So we get all answers, except those where Xs
has less than two elements.
Please note that procedurally, things happen exactly in the other direction - and that is very confusing to beginners. Even more so, since Prolog uses two "non-traditional" features: chronological backtracking, and variables - I mean real variables, meaning all possible terms - not these compile time constructs you know from imperative and functional languages. In those languages variables are holders for runtime values. Concrete values. In Prolog, variables are there at runtime, too. For more to this, see Difference between logic programming and functional programming
There is also another issue, I am not sure you understood. Think of:
?- remove(Xs, [1,2]).
Xs = [1, A, 2]
; false.
What is removed here? Nothing! Quite the contrary, we are adding a further element into the list. For this reason, the name remove/2
is not ideal in Prolog - it reminds us of command oriented programming languages that enforce that some arguments are given and others are computed. You might at first believe that this does not matter much, after all it's only a name. But don't forget that when programming you often do not have the time to think through all of it. So a good relational name might be preferable.
To find one, start with just the types: list_list/2
, and then refine list_removed/2
or list__without_2nd_last/2
.