So, here is my answer - a related question came up in the meantime.
Before answering your original question, I will look at ascending lists of integers. The reason: It's just simpler to distinguish here between the list and its elements.
Find a descriptive name
Avoid imperatives, since (pure) Prolog predicates do nothing. Or at best they describe relations. But they don't sort or add or compare. In our case ascending/1
might be an ideal name, sorted/1
is less ideal since it somewhat suggests that somebody will sort things. So there is still a tiny imp (imperative entity) around.
Give some test cases
This will often help to clarify the meaning to you. And always remember: Now, your mind is fresh, so put an effort to add meaningful test cases. Later you will become tired and less attentive, but let me assure you: Your Prolog system will never get tired. So you can put those test cases at the end of your file:
?- ascending([1,2]).
?- ascending([1,1,2]).
?- \+ ascending([2,1]).
Start with a predicate that is too general
A (trivial) first try might be:
ascending(_).
This definition succeeds always! Clearly too general. So how can we specialize it? Adding a single goal will not work. But since it should be a list - we can start with the definition of a list.
ascending([]).
ascending([_E|Es]) :-
ascending(Es).
Now, this is still to general, we want a list of integer with certain properties. So again we need to specialize this definition. What we want is that for Es = [A,B,C]
the goals A #=< B, B #=< C
hold.
:- use_module(library(clpfd)).
ascending([]).
ascending([E|Es]) :-
E #=< F,
ascending(Es).
Did you see that warning about F
being singleton? Prolog hates such variables, too often they are typos. And in a sense Prolog is right. We should relate F
to something else. It should be the next element in Es
.
ascending([]).
ascending([E|Es]) :-
lesseq_than_first(E, Es),
ascending(Es).
lesseq_than_first(_, []).
lesseq_than_first(E, [F|_]) :-
E #=< F.
We could "optimize" this definition. But this form better reveals what it is about: It is a list, were elements are interconnected.
So much for ascending lists of integers. But, how essential was the fact that the list was ascending, couldn't a descending list almost possess the same definition? Isn't this a waste of our precious attention if too many names fill up our overloaded mind?
We had essentially a chain of values that were interconnected with (#=<)/2
which could be any other relation. So instead of ascending([1,2,3])
we could have said chain(#=<, [1,2,3])
. (BTW: Is this a nice name? Or do you have a better one?)
chain(R_2, []).
chain(R_2, [E|Es]) :-
rel_to_first(R_2, E, Es),
chain(R_2, Es).
rel_to_first(_R_2, _E, []).
rel_to_first(R_2, E, [F|_]) :-
call(R_2, E, F).
With this general definition, we can now go back to your original predicate: smaller/2
is not an ideal name. Maybe shorter_than/2
?
shorter_than([], _).
shorter_than([_|Es], [_|Fs]) :-
shorter_than(Es, Fs).
sorted(Ess) :-
chain(shorter_than, Ess).
After having written this I realize that I forgot to illustrate another good advise: Play with the top-level! But this will not fit into this answer...