Net Talk
edited
by Roberto Bagnara
Editor's Note: this issue's NetTalk has been selected from
discussions taking place in the very active
SWI-Prolog Mailing
list.
Content:
Good Examples of Properly Commented Prolog Code
From: Roberto Bagnara
Can
anyone point me to (very) good examples of properly commented Prolog
code? I am looking for coding and commenting styles and practices
upon which to base the development of rather big applications.
|
From: Steve Moyle
Subject: Re: Good Examples of Properly Commented Prolog Code
One place to start would be Covington's guidlines:
[PDF] Some Coding Guidelines for Prolog
I am interested how this thread develops, particularly those that have experience of professional development in Prolog.
From: Paulo Moura
Subject: Re: Good Examples of Properly Commented Prolog Code
Roberto Bagnara wrote:
> can anyone point me to (very) good examples of properly commented
> Prolog code? I am looking for coding and commenting styles and
> practices upon which to base the development of rather big
> applications.
Maybe the following page will give you some ideas:
From: Richard A. O'Keefe
Subject: Re: Good Examples of Properly Commented Prolog Code
Steve Moyle wrote:
> One place to start would be Covington's guidlines:
>
> [PDF] Some Coding Guidelines for Prolog
> http://www.ai.uga.edu/mc/plcoding.pdf
I have a few quibbles with that.
1.1 Begin every predicate (except auxiliary predicates) with an introductory comment in the standard format.
Quibble: There is no "THE" standard format. The form I use begins with
% <name>(<mode><var>: <type>, ..., <mode><var>: <type>)
% is true when .....
Covington is half right when he says that the modes + - ? are
"not part of the Prolog language"; the thing is that they WERE
part of the DEC-10 Prolog language.
It can be useful to extend the set of modes:
- * (not in Covington) ground on entry
- + nonvar on entry
- - var on entry
- > (not in Covington) thought of as output but might be nonvar
- ? not specified
- = (not in Covington) not necessarily ground, but not further bound
so for example it is simply wrong to write
compare(?R: order, +T1: term, +T2: term)
because T1 and T2 are allowed to be variables, and
compare(?R: order, ?T1: term, ?T2: term)
is misleading because we expect that ? arguments will be unified with something, but
compare(?R: order, =T1: term, =T2: term)
is just right.
In DEC-10 Prolog, - really truly genuinely did mean "MUST
be uninstantiated", and code that got that wrong could go
seriously insane at run time. Using "-" to mean "normally
uninstantiated" is seriously misleading and I wish Covington
hadn't recommended that. The difference between > and ? is that a ?
argument may be allowed to control what the predicate does, but a
> argument should not.
Covington advises that "Comments of this type are not needed for
auxiliary predicates" on the grounds that "users of your code need not
know about" them.
I agree that anything users of your code need to know about
should be commented. I do NOT agree that auxiliary
predicates do not need to be commented. The convention we used at
Quintus was that any auxiliary predicate in a library file which
needed commenting got a comment exactly like any other predicate,
except that the first line started with "%. " instead of
"% ". We had a tool (which I wrote) that
extracted comments for a searchable catalogue, and it knew to ignore
"%." comments.
Auxiliary predicates in Prolog often correspond to loops in other
languages; the meaning of the Prolog predicate is the
loop invariant, and really should be stated in a comment.
1.2 Use descriptive argument names in the introductory comment; they need not be the same as those in the clause.
Quibble: they need not, but there should be an extremely
obvious family relationship. (Possibly a qualifying prefix
or a numeric suffix.) Normally, the names *should* be the
same whenever that is practical.
2.1 Make all names pronounceable.
Absolutely!
2.2 Never use two different names that are likely to be pronounced alike.
Also true, although there is much more variation in the
pronunciation of English than some Americans appear to realise, so
it can be quite hard to tell how things will be pronounced.
(I would never have believed that some people would pronounce
'char' as 'care' if I hadn't heard it for myself. I do NOT
regard it as reasonable to go out of my way to avoid using both
'char' and 'care'.)
2.3 Construct predicate names with lower-case letters, separating words with underscores.
Absolutely! The single most evil thing about Java was
including "_" in the language and then telling people to use
baStudlyCaps.
2.4 Do not mix up to, two, and too.
Saving characters was necessary in languages with short
identifiers (Fortran up to and including Fortran 77, limit 6; C89
with limit 8 for external identifiers) or file systems with short
names (V7 UNIX, 14 characters; MS-DOS, 8+3). Prolog is not such a
language.
2.5 Within names, do not express numbers as words.
Quibble: you *can't* make spellings 100% predictable
from pronunciations. The route from spellings to
pronunciations is about 99% predictable in English, but there are
relics of two different scribal traditions and spelling is not 100%
predictable from pronunciation. For example, "blue" and
"blew" sound the same.
I recommend that exported predicates should not have numeric suffixes
unless the number is some sort of code number. For example,
unicode_4_0_0(?Code, ?Class)
might be reasonable.
2.6 Identify auxiliary predicates by appending _aux or _x, _xx, and so forth.
There are no occurrences of that convention in the Quintus
library and I am glad of it. _x and _xx are particularly
bad; even numbers are better than that. If an auxiliary
predicate is there to do a case analysis, '_case' is a better suffix
than '_aux'. If an auxiliary predicate is a loop, '_loop' is a
better suffix than '_aux'. There is practically *always*
something better to use than '_aux'.
I am as lazy as any other programmer. But I did eventually
notice that I could *type* a short predicate name and get the
computer to expand it to something longer, either by using vi or
emacs abbreviations or by simply going back and search/replacing.
The thing that is particularly bad about _x _xx _xxx is that
they all look pretty much alike; at least _1 _2 _3 do not resemble
each other that much.
2.7 If a predicate represents a
property or relation, its name should be a noun, noun phrase,
adjective[, adjectival phrase], prepositional phrase (????), or
indicative verb phrase.
See Ledgard & Tauer, "Professional Software", for more
detailed advice on naming. Do _not_ call everything in sight
"is_xxx".
2.8 If a predicate is
understood procedurally - that is, its job is to do something rather
than to verify a property - its name should be an imperative verb
phrase.
Ledgard & Tauer again. The second evil thing about Java is
its massive overuse of "get". Do not use "get" for fetching
a property.
2.9 Place arguments in the following order: inputs, intermediate results, and final results.
This is a rather oversimplified version of the advice in "The Craft of Prolog".
2.10 Consider how your arguments map onto ordinary English.
Or whatever language you prefer to think in, of course.
3.1 Use descriptive names for variables wherver possible, and make them accurate.
Good naming practice in any language.
3.2 Construct variable names with mixed-case letters, using capitalisation to set off words.
This was the convention that I followed for many years. The
longer I live, the less I like it. Covington (and the
younger O'Keefe) were right to prefer ResultSoFar to
Result_so_far, but I tend to think these days that Result_So_Far
is even better.
3.3 For variables of purely local significance, use single letters.
ARGH! Since all variables in Prolog are local to a single
clause, surely _all_ variables are "of purely local
significance"? I once had the misfortune to have to maintain
some code written in this style and I never want to have to do
that again.
The question is "how hard is it to read the code". If there is anything tricky about it, give your reader some help.
If you have a file-wide convention that "N is always the total
number of elements to process, I is the number of elements
processed so far" then you can freely use N and I. If you
have a convention that L and U are always the lower and upper
bounds of some range, fine. If you have a convention that C0, C1,
C2, C are always character codes, S0, S1, S2, S are sequences, and so
on, fine. But that convention must *not* be "purely local".
3.4 Use a single letter for the first element of a list and a plural name for the remaining elements.
This is a bent, even broken, version of the usual rule, which
is "use a singular name for list elements and the corresponding
plural name for the whole list". I regard Covington's
[T|Tree] example as *bad* because it is inconsistent. If one
thing is a T, why aren't several of them Ts? If several of
them are Trees, why isn't one of them a Tree? [Tree|Trees]
is best, [T|Ts] is OK (if there is something in the context that
helps you figure out that T means Tree), but [T|Tree] is not good
at all.
4.1 Use /* */ only to comment out blocks of code; use % for explanatory comments.
I strongly disagree. For one thing, you really should not
be 'commenting out', and if you are, /* */ comments don't
actually work (as C programmers, if they are any good, are well
aware).
In a typical Prolog source file, you will have some sort
of standard header with meta-data about the file (name,
author, revision, copyright, &c). Then you'll have
Prolog :- module declaration and maybe other declarations.
Any then you should have a big comment explaining what the file is
all about and what the central ideas are, with maybe a picture of a
data structure.
This code should be readable as plain text with a minimum
of distracting punctuation. That means that it has to be a
/* */ comment, with *no* extra stars. (But possibly dashes at
the top and bottom.)
If a file divides naturally into several pieces, each piece can have its own such comment.
4.2 Use layout to make comments more readable.
Use layout to make *anything* more readable.
4.3 If code needs elaborate explanation, consider rewriting it.
Sound advice for any language, but sometimes the result of
consideration will be "nope, it's already as simple as we
could hope for, it's just a hard problem".
4.4 Indent all but the first line of each clause.
Yes, and begin each clause on a new line.
4.5 If a test is unnecessary because a cut has guaranteed that it is true, say so in a comment at the appropriate place.
I'm not sure that this is always necessary; I am sure that it never hurts.
4.6 Indent an additional 2 spaces between 'repeat' and the corresponding cut.
I agree about indenting, but why 2? I use 4. Also, sometimes the
logically final cut is not physically final and shouldn't be.
4.7 Put each subgoal on a separate line, except closely related pairs such as write and nl.
Generally good advice. If you are willing to use format/2, you
don't even need to put write and nl on the same line. It's a
great pity the ISO Prolog substandard didn't include anything
like format/2.
4.8 (A) Skip a line between clauses. (B) Skip two lines between predicates.
- (A)
I flatly disagree. Amongst other things, my editor lets me
move to the beginning of the previous procedure with one
(Meta-Ctrl-R) command (or the next, with Ctrl-_
Meta-Ctrl-R). Sticking in those silly blank lines stuffs
this up completely, and it is far too nice a feature to let
someone stuff it up.
- (B) I use more blank lines between unrelated predicates than with a group consisting of a predicate and its auxiliaries.
4.9 Keep clauses less than 25 lines if possible.
So they fit on a 24x80 screen, of course. I've never paid much
attention to this in languages like Fortran and C. Since in
Prolog you *can't* have a loop without splitting out another predicate,
and case analyses *often* (but not always) deserve their own predicates
too, it is no hardship to follow this rule in Prolog.
4.10 Consider redesigning any non-auxiliary predicate that has more than 4 arguments.
I count roughly 70 such exported predicates in the Quintus library. For example,
% anti_unify(+Term1, +Term2, -Subst1, -Subst2, -Term)
% binds Term to a most specific generalisation of Term1 and Term2,
% and Subst1 and Subst2 to substitutions such that
% Subst1(Term) = Term1
% Subst2(Term) = Term2
% Substitutions are represented as lists of Var=Term pairs, where
% Var is a Prolog variable, and Term is the term to substitute for Var.
% When you call it, Subst1, Subst2, and Term should be variables.
This has 2 inputs and 3 outputs; it's hard to see how it could have fewer than 5 arguments, and it only does one thing.
% ask_number(+Prompt, +Lower, +Upper, +Default, >Answer)
% asks a question where the answer should be a number in the range
% Lower..Upper. If the user enters an empty line, the Default
% (which need not be in range, or even a number) is returned.
% A typical use might be ask_number('Percentage', 0, 100, 50, Percentage).
This has four inputs and one output. I suppose the four
arguments could be packed into a data structure, but that data
structure would have no other uses. In fact, it would likely
make uses of this predicate *more* complicated, not less, to try
to reduce the arity.
I will agree that benchmark:time/15 has far too many arguments, but I didn't write that one.
% substring(+ABC: text(), ?B: text(),
% ?A_Len: integer, ?B_Len: integer, ?C_Len: integer)
% is true when ABC and B are both strings or both atoms and
% there exist texts A and C such that ABC = A ++ B ++ C,
% length(A) = Len_A, length(B) = Len_B, length(C) = Len_C.
% The whole string argument (ABC) must be supplied;
% this predicate can solve for all the others.
Again, this predicate *needs* 5 arguments.
Also bear in mind the possibility of using term-expansion
to supply extra arguments in a systematic way. A
non-terminal with 4 arguments is every bit as reasonable as a
predicate with 4 arguments, even though a non-terminal with 4
arguments "is" a predicate with 6 arguments (or in some
translations, 7 or even 8).
4.11 Consider using a pretty-printer for finished printouts.
The only time a program is "finished" is when its scrapped. If you
want fancy printing, use a literal programming tool. I've used
fancy listers (this was standard in Interlisp-D), and I came to
hate things that changed the font size. What if I wanted to
concentrate on the comments rather than the procedure
headings? Why would I want the comments in italics or in teeny
tiny script or changed so they don't look like comments?
(Having said that, I have a fontiser/colouriser of my own. On very
rare occasions it is useful. But for ordinary
listsings, it's a pain. It is especially a pain because what
you see in the listing DOES NOT MATCH what you see in Emacs.
5.1 Invest appropriate (not excessive) effort in the program; distinguish a prototype ffrom a finished product.
Good advice for any language.
5.2 The most efficient program is the (sic.) one that does the right computation, not the one with the most tricks.
Of course there may be more than one program that does a
right computation. The old (Kernighan?) advice "make it
simple to make it fast" still holds.
5.3 When efficiency is critical, make tests.
Absolutely!
5.4 Conduct experiments by writing separate small programs, not by mangling the main one.
Nope. Small programs tell you about the performance of
small programs, not the real program. The only satisfactory
way to tell if a change will help the real program is to change
the real program and measure it. Covington seems to be
advising people who do not have or do not use any version control
system.
Make sure the working sources are checked into version
control because changing them for "efficiency". Consider
making a new version control branch for efficiency experiments.
5.5 Use cuts sparingly but precisely.
Nice advice, but how? See The Craft of Prolog.
5.6 Never add a cut to correct an unknown problem.
Absolutely!
5.7 Avoid the semicolon (;) - make separate clauses instead.
I disagree. His example is very artificial. I originally
followed this advice, until it dawned on my that most of the time
there *wasn't* any natural concept and it was extremely hard to
give the case analysis predicate any sensible name.
The important thing here is not avoiding semicolons but giving
things their own identities and names whenever there is a helpful name
that they *can* be given.
5.8 Use parentheses whenever operator precedence is important: do not assume that people have memorised the precedence table.
Hmm. Why don't we just abandon operator syntax entirely then
and just use Lispy syntax? (Actually, I *like* Lispy syntax.)
In the specific case of x, y ; z I agree that parentheses are advisable, but I'd write it as
( x,
y
; z
)
where
the layout provides the important clue about precedence I find
code using otiose parentheses like (x, y) ; z disgusting and
difficult to read.
The important thing here is
- whenever there are so many or such rare operators in a part of the program that people might plausibly be confused, rewrite it.
5.9 When you use ; always use parentheses
But around the *whole* form, *not* around the conjunctions.
5.10 Avoid if-then-else structures.u
I strongly disagree. Very strongly indeed. If-then-elses
are MUCH better than cuts. This is especially true in Mercury, of
course.
5.11 Look out for constructs that are almost always wrong.
True.
5.12 Work at the beginning of the list
True, but don't be fanatical about it.
5.13 Avoid append as far as possible.
Use append liberally in *design*; use difference pairs or grammar rules in *implementation*.
5.14 Use difference lists to achieve fast concatenation.
Quibble: I regard the term "differnce lists" as misleading. I use
"difference pairs" (of arguments) or "list differences". Apart from
that, fine.
There are other ways to get fast concatenation, of course.
5.15 Use tail recursion for recursions of unknown depth.
I don't see what "unknown depth" has to do with it. I would say
"prefer tail recursion to body recursion, but don't be scared of
body recursion if you need it".
5.16 Recognise that tail recursion is unimportant when the depth of the recursion is limited to about 50 or less.
I agree that you should not "sacrifice simplicity and clarity" to
achieve microscopic gains, but I have never found it useful to ask
"is this recursion limited to about 50 or less". Even for
short recursions (and what counts as "short" on a 1GB machine is
different from what used to count as "short" on 1MB machiens) tail
recursion is often the most natural way to express things.
I'd say that the rule is "use tail recursion whenever it is
a clear way to say what you want, WHATEVER the likely depth
of recursion; use body recursion if that's the easiest way to
get the code right, WHATEVER the likely depth of recursion".
Worry about restructuring *after* you have measured what the code
is actually doing.
5.17 Avoid assert and retract unless you actually need to preserve information through backtracking.
I would also say "If you have a dynamic predicate, write
interface predicates for changing it instead of using 'bare'
calls to assert and retract, so that your interface predicates can
check that the data base will still be logically consistent after
the change."
5.18 For sorting, use merge sort or a built-in sorting algorithm.
He has the details wrong: Quicksort is *NOT* O(n log n), it is
O(n^2) and this worst case _often_ happens in practice. (Yes, I
know about the arguments in textbooks that say it's
extremely unlikely for uniform random inputs; the problem is that
real-world inputs are not uniform random inputs.)
5.19 Instead of sorted lists, consider using trees.
"A binary tree never needs sorting because it is never out of
order"; however, it DOES need work, sometimes rather complicated
work, to KEEP it in order. In a WAM-based implementation
where a list requires 2 words per element and other compound
terms with N arguments require N+1 words, a tree will cost twice as
much storage as a list.
The *right* rule is "use binary trees if you need element-wise access;
use sorted lists if you can do most things on whole collections."
6.1 Isolate non-portable code.
Good advice for any language.
6.2 Isolate "magic numbers".
In fact the example of pi is a very good one. I once had to use
a Prolog system from one of our competitors where one part of
their library used a 64-bit value for pi and another part used an
80-bit value for pi. Not surprisingly, trig code got rather
messed up.
6.3 take the extra minute to prevent errors rather than having to find them later.
Good advice for any language.
6.4 Test code at its boundaries (limits).
Good advice for any language.
6.5 Test that each loop starts correctly, advances correctly, and ends correctly.
Good advice for any language; could go further.
6.6 Test every predicate by forcing it to backtrack.
Misleadingly worded: many predicates *can't*
backtrack. "Test every predicate by failing back into it"; now
that's something you *can* do.
6.7 Test predicates by supplying arguments of the wrong types.
I'm not so sure about this one. For a beginner, it may be
useful advice so that they find out what is likely to provoke
which error message. But append/3, for example, is designed to
work with lists; it isn't *intended* to do anything in particular if
given arguments of any other type, so there is no sense in which you
can test it for other types. (To test something, there must be a
specified set of acceptable behaviours so that you can tell whether the
actual behaviour is acceptable or not. If "anything goes", then
the "test" cannot fail.)
6.8 Do not waste time testing for errors that will be caught anyhow.
That'd be nice, except that we do not in general *know*
which errors "will be caught anyhow". 6.8 appears to
contradict 6.7.
6.9 In any error situation, make the program either correct the problem or crash (not just fial).
True. "Crashing" here means calling raise_exception/1 (which
the ISO Prolog substandard very confusingly renamed to throw/1).
6.10 Make error messages informative.
Good advice, but error reports should be related to the exported
predicate they originated from, *not* to some internal
predicate which the user doesn't know about.
When we added exception handling to Quintus Prolog, some
built-in predicates were given error handlers just so that they
could handle internal deeply generated exceptions and map them
back to user-level descriptions.
6.11 Master the Prolog debugger; it is simple, powerful, and portable.
Simple, yes. Powerful, yes, although it's even better with
the advice package. Portable, not as much as it could
be. There are 3-port, 4-port, 5-port, and even 7-port
debuggers out there, and that's without considering exceptions. I
find it difficult to use the SWI debugger because the keystrokes
that years of using Quintus Prolog programmed into my fingers
don't work in SWI. (One of the reasons I'd rather
--disable-readline.)
6.12 Use write for debugging.
Wrong. Use print for debugging; it was specifically intended for that.
6.13 Mark all temporarily altered lines of code with "%TEST!!!".
I don't find this very useful; if I want to know what I changed, I just ask SCCS.
6.14 Use write(!!!) to mark places in the program where work remains to be done.
I've never found that particularly useful either.
Something slightly more useful is
shouldnt :-
write(user, 'This should not happen'), nl(user),
break,
throw(shouldnt).
(adapted from Interlisp).
From: Michael Maxwell
Subject: Re: Good Examples of Properly Commented Prolog Code
Richard O'Keefe wrote:
> 6.13 Mark all temporarily altered lines of code with "%TEST!!!".
>
> I don't find this very useful; if I want to know what I changed,
> I just ask SCCS.
>
> 6.14 Use write(!!!) to mark places in the program where work remains
> to be done.
>
> I've never found that particularly useful either.
Sometimes these comments persist through multiple revisions of a file, so SCCS or RCS aren't useful ways to point them out.
FWIW, my favorite editor (Visual SlickEdit) allows color coding of
constructs on a programming language-specific basis. Most editors
these days do (including jEdit).
What dawned on me awhile back is that my editor allows me to define
user-specific tokens, with their own color coding, and I can use that
to put tokens like '%TEST!!!' or '%!!!' in some color that stands out,
like red. That way temporarily altered lines, or things that I
want to fix but don't have the time to do right now, are easily spotted
when I scroll through a buffer. (If I used TreeWare, and a color
printer, this would work too.)
This color coding of course doesn't work for someone else who is
reading my file, unless we standardize the user-defined tokens.
But Michael Covington's suggested use of '!!!' at least provides some
visual cue.
From: Jan Wielemaker
Subject: Re: Good Examples of Properly Commented Prolog Code
Richard A. O'Keefe wrote:
> Steve Moyle wrote:
>> One place to start would be Covington's guidlines:
>>
>> [PDF] Some Coding Guidelines for Prolog
>> http://www.ai.uga.edu/mc/plcoding.pdf
>
> I have a few quibbles with that.
Not sure my understanding of `a few' is the same as yours :-)
> The form I use begins with
>
>
% <name>(<mode><var>: <type>, ...,
<mode><var>: <type>)
> % is true when .....
I like this very much, except I'd like to have a blank line between the
first semi-formal line and the description, starting with % Sofar I
didn't use types, but I might start doing that. For a long time I plan
to add some code to the help-system that makes this documentation
available in a transparent way. Still didn't find the time to do it :-(
Here are is a typical example of what I use
% age(?Name, ?Age)
%
% True if person named Name has age Age.
> 2.6 Identify auxiliary predicates by appending _aux or _x, _xx,
> and so forth.
>
> There are no occurrences of that convention in the Quintus library
> and I am glad of it. _x and _xx are particularly bad; even numbers
> are better than that. If an auxiliary predicate is there to do
> a case analysis, '_case' is a better suffix than '_aux'. If an
> auxiliary predicate is a loop, '_loop' is a better suffix than
> '_aux'. There is practically *always& something better to use
> than '_aux'.
>
> I am as lazy as any other programmer. But I did eventually notice
> that I could *type* a short predicate name and get the computer to
> expand it to something longer, either by using vi or emacs
> abbreviations or by simply going back and search/replacing.
>
> The thing that is particularly bad about _x _xx _xxx is that they
> all look pretty much alike; at least _1 _2 _3 do not resemble each
> other that much.
Not so sure. I still struggle with the naming of auxiliary predicates.
_loop is nice if it applies, but quite often there is no sensible name
to give to them. _x* works fine to about 3-4 levels, which is often
enough. To me _xx and _2 are about equal.
> This was the convention that I followed for many years. The longer
> I live, the less I like it. Covington (and the younger O'Keefe)
> were right to prefer ResultSoFar to Result_so_far, but I tend to think
> these days that Result_So_Far is even better.
I prefer ResultSoFar.
> 4.6 Indent an additional 2 spaces between 'repeat' and the corresponding
> cut.
>
> I agree about indenting, but why 2? I use 4.
> Also, sometimes the logically final cut is not physically final
> and shouldn't be.
I used to do that, but since the days of auto-indenting editors things
get more tricky. Because where I like this for repeat, it should
apply to any generator in a failure-driven loop. I used to write
( member(X, List),
process(X),
fail
; true
)
But even PceEmacs cannot determine when this layout is appropriate and
I hate doing the indentation by hand. So I stopped using
this. Today I use something like this:
( repeat,
action,
condition
-> true
).
Where applicable, use forall(generator(X), action(X)). It also
avoids failure of action/1 go unnoticed. Better a program saying
'No' than producing the wrong result.
> 4.7 Put each subgoal on a separate line, except closely related pairs
> such as write and nl.
>
> Generally good advice. If you are willing to use format/2, you
True, but quite a few interesting systems have it and the implementations are reasonable compatible.
> 4.8 (A) Skip a line between clauses.
> (B) Skip two lines between predicates.
>
> (A) I flatly disagree. Amongst other things, my editor lets me move
> to the beginning of the previous procedure with one (Meta-Ctrl-R)
> command (or the next, with Ctrl-_ Meta-Ctrl-R). Sticking in
> those silly blank lines stuffs this up completely, and it is far
> too nice a feature to let someone stuff it up.
I agree with Richard. Next clause on next line, next related
predicate one blank line and next unrelated predicate two blank lines
works just fine. The not indented clause head is enough to
seperate the clauses.
> 4.9 Keep clauses less than 25 lines if possible.
>
> So they fit on a 24x80 screen, of course.
>
> I've never paid much attention to this in languages like Fortran and
> C. Since in Prolog you *can't* have a loop without splitting out
> another predicate, and case analyses *often* (but not always)
> deserve their own predicates too, it is no hardship to follow this
> rule in Prolog.
Unless you have I/O like (including graphics calls) predicates ... I've
seen _very_ long clauses, even overflowing the 512 subclause limit the
system had very long ago (now unlimited).
> 4.11 Consider using a pretty-printer for finished printouts.
>
> The only time a program is "finished" is when its scrapped.
> If you want fancy printing, use a literal programming tool.
> I've used fancy listers (this was standard in Interlisp-D),
> and I came to hate things that changed the font size. What
> if I wanted to concentrate on the comments rather than the
> procedure headings? Why would I want the comments in italics
> or in teeny tiny script or changed so they don't look like
> comments?
>
> (Having said that, I have a fontiser/colouriser of my own.
> On very rare occasions it is useful. But for ordinary listsings,
> it's a pain. It is especially a pain because what you see in
> the listing DOES NOT MATCH what you see in Emacs.
Might be a bit outdated. Who prints programs these days with nice large
colour displays? I've not printed code for years. Do get yourself a
1280x1024 or bigger monitor. Much better investment than a faster CPU.
> 5.3 When efficiency is critical, make tests.
>
> Absolutely!
Use the profiler. Use the graphical debugger to examine
choice-points and see whether they are as you expect them to be.
Be aware that tests are good, but performance depends on the Prolog
environment. I try to ensure performance of a particular
construct does not deteriorate much in later versions, but sometimes an
alternative construct may become much faster.
> 5.5 Use cuts sparingly but precisely.
>
> Nice advice, but how? See The Craft of Prolog.
True.
> 5.10 Avoid if-then-else structures.
>
> I strongly disagree. Very strongly indeed. If-then-elses are
> MUCH better than cuts. This is especially true in Mercury, of course.
I tend to use a predicate if I can give it a meaningful name, i.e. if
by giving it a name the readability of the main clause improves.
Otherwise use if-then-else.
> 5.17 Avoid assert and retract unless you actually need to preserve
> information through backtracking.
>
> I would also say "If you have a dynamic predicate, write interface
> predicates for changing it instead of using 'bare' calls to
> assert and retract, so that your interface predicates can check
> that the data base will still be logically consistent after the
> change."
These seems more complimentary than conflicting statements. Both are
true. Using assert/retract as a substitute for global variables is a
really bad idea. It often harms performance, it make the nice `retry'
option of the debugger worthless, it has trouble with cleanup,
re-entrance and threads. If you really need them, write an
interface as Richard suggests. Especially true in multi-threaded
applications as you probably need explicit synchronisation using
mutexes.
> 6.4 Test code at its boundaries (limits).
>
> Good advice for any language.
Time for a good documented testing skeleton ... There are various out there of different quality.
> 6.11 Master the Prolog debugger; it is simple, powerful, and portable.
>
> Simple, yes. Powerful, yes, although it's even better with the
> advice package. Portable, not as much as it could be. There are
> 3-port, 4-port, 5-port, and even 7-port debuggers out there, and
> that's without considering exceptions. I find it difficult to use
> the SWI debugger because the keystrokes that years of using Quintus
> Prolog programmed into my fingers don't work in SWI. (One of the
> reasons I'd rather --disable-readline.)
It took me very long, mostly because after I write it hardware was too
slow, but these days I rarely touch the normal debugger anymore. 99% of
the cases I use the graphical one. Unfortunately sometimes it
goes nuts :-(
> 6.12 Use write for debugging.
>
> Wrong. Use print for debugging; it was specifically intended for that.
use debug(Channal, Format, Arguments). You can leave them in your code
with no overhead if you compile using -O and they act as comments too.
> 6.13 Mark all temporarily altered lines of code with "%TEST!!!".
>
> I don't find this very useful; if I want to know what I changed,
> I just ask SCCS.
True. I strongly dislike files with commented dead code. Only, use CVS or subversion :-)
> 6.14 Use write(!!!) to mark places in the program where work remains
> to be done.
>
> I've never found that particularly useful either.
>
> Something slightly more useful is
>
> shouldnt :-
> write(user, 'This should not happen'), nl(user),
> break,
> throw(shouldnt).
>
> (adapted from Interlisp).
Most of the time I put % TBD: whatever at the end of the line.
Sometimes I simply call tbd('Whatever'). That will cause the system to
raise an undefined predicate. Even better, list_undefined/0 will report
them and where they are called.
From: Richard O'Keefe
Subject: Re: Good Examples of Properly Commented Prolog Code
Jan Wielemaker wrote:
> I like this very much, except I'd like to have a blank line between the
> first semi-formal line and the description, starting with %
When the comment starts out like this:
% <name>(<mode><var>: <type>, ...,
<mode><var>: <type>)
% is true when .....
the first line is the subject of the first sentence. It is really very jarring
to put a blank line in the *middle* of a sentence.
> % age(?Name, ?Age)
> %
> % True if person named Name has age Age.
I would write that as
% age(?Name: atom, ?Age: number)
% is true when Name is the name of a person
% and the age of that person in years is Age.
or without the types as
% age(?Name, ?Age)
% is true when Name is the name of a person (as an atom)
% and the age of tha person in years is Age (as a number).
In this case, I might leave out the (as a number) bit; what else would
it be? But the encoding of the name is _not_ obvious and needs
saying. A general convention within a file that names are atoms
(as opposed to character lists or strings or even name(Family,Personal)
or whever) would mean you don't have to say it every time.
>> 2.6 Identify auxiliary predicates by appending _aux or _x, _xx,
>> and so forth.
>
> Not so sure. I still struggle with the naming of auxiliary
> predicates.
Why, so do I.
> _loop is nice if it applies, but quite often there is no
> sensible name to give to them. _x* works fine to about 3-4
> levels, which is often enough. To me _xx and _2 are about
> equal.
I disagree. I don't find that _x works to *any* number of levels,
and I have real trouble with the idea of 4 levels of "miscellaneous"
auxiliary predicates in the first place. Given that one of the
first Prolog programs I worked on was a geometry theorem prover (I
didn't write the thing, I just revised it), I still think of _x as
referring geometric co-ordinate and expect the next two in the series
to be _y and _z.
Perhaps we could advance this discussion further if Jan provided an
example where he wanted to use _x _xx and _xxx and see what I can do
about naming them.
>> This was the convention that I followed for many years. The longer
>> I live, the less I like it. Covington (and the younger O'Keefe)
>> were right to prefer ResultSoFar to Result_so_far, but I tend to think
>> these days that Result_So_Far is even better.
>
> I prefer ResultSoFar.
As I said, I _used_ to. These days, I am less tolerant of things that violate the normal spacing rules for text.
>> 4.6 Indent an additional 2 spaces between 'repeat' and the corresponding
>> cut.
>>
>> I agree about indenting, but why 2? I use 4.
>> Also, sometimes the logically final cut is not physically final
>> and shouldn't be.
>
> I used to do that, but since the days of auto-indenting editors
When I first heard about Emacs, I was ever so keen. When I actually got
my hands on Emacs, it was a real disappointment. Emacs'
auto-indentation for C was *horrible*, and it turned out to be
impossible to reach a tolerable style by fiddling with its parameters.
To this day, one of the first things I do with an autoindenting editor
is switch automatic indentation *OFF*.
The editor I use is Emacs-*like*. For indentation,
Return -> make a new line with no indentation below the
current line, move the cursor to the new line
^_ Return -> like Return, but new line is above old line,
Line feed -> make a new line with the same indentation as the
current line, below the current line, and move
the cursor to it (just after the indentation)
^_ Line feed-> like Line feed, but new line is above old line.
Meta ^I -> indent current line by one step (default 4,
settable by ^U n Meta +)
^U n $^I -> indent by n steps
Meta ^U -> outdent current line by one step
^U n $^U -> outdent by n steps
Meta i -> indent current region (mark to point) one step
^U n $i -> indent current region n steps
Meta u -> outdent current region (mark to point) one step
^U n $u -> outdent current region n steps
> things get more tricky.
Indeed things get tricky. That's because all the autoindenters I've come across are far more trouble than they are worth.
> Because where I like this for repeat, it should apply
> to any generator in a failure-driven loop. I used to write
>
> ( member(X, List),
> process(X),
> fail
> ; true
> )
OK, how would we do that in my editor?
"( member(X, List)," ^J 21
$^I $^I "process(X)," ^J 16
$^U "fail" ^J
7
$^U "; true" ^J 11
")"
1
Total:
56 keystrokes.
Who needs an auto-indenter? Hmm. I've just added a very simple auto-indent feature. If enabled,
"(" at the start of a line adds indent-1 spaces after it;
";" at the start of a line indents, adds ";", and indent-1 spaces;
"->" at the start of a line indents, adds "->", and indent-2 spaces;
")" at the start of a line outdents unless the previous line
began with "(" ";" or "->".
LF indents the new line one step if the previous line starts
with "(" ";" or "->".
So we get
"(member(X, List)," ^J 18
$^I "process(X)," ^J 14
$^U "fail" ^J
7
";true" ^J
6
")"
1
Total:
46 keystrokes.
Autoindenting doesn't save all that many keystrokes.
But I actually disagree. I do NOT think "it should apply to any
generator in a failure-driven loop". There is something very very
special about repeat/0 which does NOT apply to member/2 (at least in
normal use), namely that repeat/0 never fails. It has and is
intended to have infinitely many solutions. My layout for
failure-driven loops is
( member(X, List)
process(X),
fail ; true
),
just try and teach _that_ to an auto-indenter. Note the signature
of a failure-driven loop: "fail ; true". A repeat loop does
NOT have that signature. A repeat loop has to be terminated by
some kind of cut.
> Where applicable, use forall(generator(X), action(X)).
Agreed.
> Better a program saying 'No' than producing the wrong result.
Absolutely!
>> 4.9 Keep clauses less than 25 lines if possible.
>>
>> So they fit on a 24x80 screen, of course.
>>
>> I've never paid much attention to this in languages like Fortran and
>> C. Since in Prolog you *can't* have a loop without splitting out
>> another predicate, and case analyses *often* (but not always)
>> deserve their own predicates too, it is no hardship to follow this
>> rule in Prolog.
>
> Unless you have I/O like (including graphics calls) predicates
> ... I've seen _very_ long clauses, even overflowing the 512
> subclause limit the system had very long ago (now unlimited).
At Quintus we had a bug report from a customer: they had a clause
with more than 64000 variables! It was machine-generated, of
course.
>> 4.11 Consider using a pretty-printer for finished printouts.
>
> Might be a bit outdated. Who prints programs these days with
> nice large colour displays?
Me.
> Do get yourself a 1280x1024 or bigger monitor.
I *have* a 1280x1024 monitor. In fact both my SunBlade100 and my
G4 Mac have such monitors. You *still* don't get a whole lot of
text on them, and if you want to really carefully read a file, paper is
*wonderful*. I tend to find that syntax colouring just makes stuff less
readable (because it reduces the contrast, which is well known to
reduce readability).
>> 5.10 Avoid if-then-else structures.
>>
>> I strongly disagree. Very strongly indeed. If-then-elses are
>> MUCH better than cuts. This is especially true in Mercury, of course.
>
> I tend to use a predicate if I can give it a meaningful name, i.e. if by
> giving it a name the readability of the main clause improves. Otherwise
> use if-then-else.
A difference of emphasis here, but we actually agree. Finding it hard
to assign a name to something is a good clue that you should not be
splitting it out.
>> 5.17 Avoid assert and retract unless you actually need to preserve
>> information through backtracking.
>>
>> I would also say "If you have a dynamic predicate, write interface
>> predicates for changing it instead of using 'bare' calls to
>> assert and retract, so that your interface predicates can check
>> that the data base will still be logically consistent after the
>> change."
>
> These seems more complimentary than conflicting statements.
They are complementary, that's why I said "I would ALSO say"...
>> 6.12 Use write for debugging.
>>
>> Wrong. Use print for debugging; it was specifically intended for that.
>
> use debug(Channal, Format, Arguments). You can leave them in your code
> with no overhead if you compile using -O and they act as comments too.
Good advice for SWI Prolog. The point I was making is that
print/1 exists so that output can be tailored (and in particular,
heavily and appropriately abbreviated) for debugging, while write/1
insists on showing you everything. Combine the advice: if
you use debug/3, use the ~p format rather than the ~w format.
> True. I strongly dislike files with commented dead code. Only, use
> CVS or subversion :-)
For a single person project, SCCS or RCS are pretty much ideal. The CVS documentation has me completely baffled.
> Most of the time I put % TBD: whatever at the end of the line. Sometimes
> I simply call tbd('Whatever'). That will cause the system to raise an
> undefined predicate. Even better, list_undefined/0 will report them and
> where they are called.
The problem with comments like % TBD is that they are still there 4
years and 7 releases later. tbd/1 and list_undefined/0 are
excellent (albeit SWI-specific) advice.
From: Jan Wielemaker
Subject: Re: Good Examples of Properly Commented Prolog Code
We're getting close to consensus :-)
Richard A. O'Keefe wrote:
> Jan replied to my comments on Covington.
> I like this very much, except I'd like to have a blank line between the
> first semi-formal line and the description, starting with %
>
> When the comment starts out like this:
>
% <name>(<mode><var>: <type>, ...,
<mode><var>: <type>)
> % is true when .....
> the first line is the subject of the first sentence.
> It is really very jarring
I see. I prefer to see it as a synopsis like the traditional Unix
manpages. One of the reasons is different patterns. What
about
% age(+Name, -Age)
% age(-Name, +Age)
%
% ....
Are you going for
% age(+Name, -Age)
% is true if ...
%
% age(-Name, +Age)
% is true if ...
I also tend to document different arities in the same comment, like this:
% rdf_load(+File)
% rdf_load(+File, +Options)
%
% ....
rdf_load(File) :-
rdf_load(File, []).
rdf_load(File, Options) :-
....
> Perhaps we could advance this discussion further if Jan provided an
> example where he wanted to use _x _xx and _xxx and see what I can do
> about naming them.
I often call the do_something, or whatever. The issue frequently
arrises on constructs as below. In this particular case
load_stream/1 would be a good name, but there are enough cases where it
is much harder to find a good name that isn't the same as the name of
the main predicate.
load_file(File) :-
open(File, read, In),
call_cleanup(do_load_file(In), close(In)).
do_load_file(...)
> I used to do that, but since the days of auto-indenting editors
>
> When I first heard about Emacs, I was ever so keen.
> When I actually got my hands on Emacs, it was a real disappointment.
> Emacs' auto-indentation for C was *horrible*, and it turned out to be
> impossible to reach a tolerable style by fiddling with its parameters.
> To this day, one of the first things I do with an autoindenting editor
> is switch automatic indentation *OFF*.
Properly trained, which unfortunately generally means sticking to the
style used by the author of the indentation module or writing your own,
auto-indenting editors are very valuable, as the fact that the
indentation goes wrong is an immediate clue you've made a mistake. For
me its 'can't program without them'. Again, its not about the number of
keystrokes (although it helps a little), its about the feedback on
errors.
> But I actually disagree. I do NOT think "it should apply to any
> generator in a failure-driven loop". There is something very very
> special about repeat/0 which does NOT apply to member/2 (at least
> in normal use), namely that repeat/0 never fails. It has and is
> intended to have infinitely many solutions. My layout for
Partly true, but in C I use the same layout for "for(;;)" and
"for(init;cond;next)". I don't see why this isn't the case in
Prolog. They are equally special and dangerous.
> failure-driven loops is
>
> ( member(X, List)
> process(X),
> fail ; true
> ),
>
> just try and teach _that_ to an auto-indenter. Note the signature
Works perfect in PceEmacs. I used to use fail ; true this
way. Don't really know why I stopped doing it, maybe I should
re-introduce it.
> Unless you have I/O like (including graphics calls) predicates
> ... I've seen _very_ long clauses, even overflowing the 512
> subclause limit the system had very long ago (now unlimited).
>
> At Quintus we had a bug report from a customer: they had a clause
> with more than 64000 variables! It was machine-generated, of course.
Thats normal. Almost all limits the initial compiler had (#subclauses,
#variables, size of produced code) had to be removed over time. The
current compiler is limited by memory only, as most of the rest of the
system.
> > 4.11 Consider using a pretty-printer for finished printouts.
>
> Might be a bit outdated. Who prints programs these days with
> nice large colour displays?
>
> Me.
>
> Do get yourself a 1280x1024 or bigger monitor.
>
> I *have* a 1280x1024 monitor. In fact both my SunBlade100 and my G4 Mac
> have such monitors. You *still* don't get a whole lot of text on them,
> and if you want to really carefully read a file, paper is *wonderful*.
> I tend to find that syntax colouring just makes stuff less readable
> (because it reduces the contrast, which is well known to reduce
> readability).
I must say I find it hard these days to read Prolog without the
colouring of PceEmacs. Guess its what you're used to. Just
keyword colouring isn't very valuable in Prolog.
>>> 6.12 Use write for debugging.
>>>
>>> Wrong. Use print for debugging; it was specifically intended for
>>> that.
>>
>>use debug(Channal, Format, Arguments). You can leave them in your code
>>with no overhead if you compile using -O and they act as comments too.
>
> Good advice for SWI Prolog. The point I was making is that print/1
Its easy enough to port the library to any system providing term_expansion or -bit less work- goal_expansion.
>> True. I strongly dislike files with commented dead code. Only, use
>> CVS or subversion :-)
>
> For a single person project, SCCS or RCS are pretty much ideal.
> The CVS documentation has me completely baffled.
You need very little for single person development. cvs add file, cvs
rm file, cvs commit, cvs update and cvs diff cover 99% of what you
need. Install web-browsing of the repository and you're all set and
done.
>> Most of the time I put % TBD: whatever at the end of the line. Sometimes
>> I simply call tbd('Whatever'). That will cause the system to raise an
>> undefined predicate. Even better, list_undefined/0 will report them and
>> where they are called.
>
> The problem with comments like % TBD is that they are still there
> 4 years and 7 releases later. tbd/1 and list_undefined/0 are excellent
True, but if the problem never became serious who cares?
> (albeit SWI-specific) advice.
Or any other system with a cross-referencer.
From: Uwe Lesta
Subject: Re: Good Examples of Properly Commented Prolog Code
I follow the discussion with a half eye.
Yes, Coding Guidelines are useful.
They are more useful if other tools like pretty_printer work with it.
but, IMO they will be commonly used if it is simple to write them and i will get immediately benefit of it.
A good sample for comments of methods are from ms visual studio The
CSharp editor has templates for comments which are automatically
inserted after typing '///' in the line befor a method definition.
and you get 'tooltips' shown by this comments if you point with the mouse on a method as well as you type the parameter list.
If you haven't seen it, try to get a lock on it.
My other point about no implicit conversion is this: If rationals stay
rational, I don't think we need to worry about sort et. al being
extended to support them. Use predsort combined with compare_rationals/3,
because mixed data will not be expected.
From: Richard O'Keefe
Subject: Re: Good Examples of Properly Commented Prolog Code
Jan Wielemaker wrote:
> I see. I prefer to see it as a synopsis like the traditional Unix manpages.
Manpages are good for Manual PAGES. That kind of layout is not designed for and not particularly good for SHORT comments.
> One of the reasons is different patterns. What about
>
> % age(+Name, -Age)
> % age(-Name, +Age)
> %
> % ....
>
> Are you going for
>
> % age(+Name, -Age)
> % is true if ...
> %
> % age(-Name, +Age)
> % is true if ...
No. ALL patterns should be TRUE in the same cases, otherwise why
do they have the same name? In that case, I'd leave the modes out
of the first line and write
% age(Name, Age)
% is true when ....
% This may be used in modes (+,-) and (-,+) but no others.
> I also tend to document different arities in the same comment,
I don't. They are different predicates. They may be related, but they are different.
Note that we are talking here about the leading comment for ONE
PREDICATE. If I want to say something about a GROUP of predicates, I
have ANOTHER comment ahead of the group which describes the group.
>> But I actually disagree. I do NOT think "it should apply to any
>> generator in a failure-driven loop". There is something very very
>> special about repeat/0 which does NOT apply to member/2 (at least
>> in normal use), namely that repeat/0 never fails. It has and is
>> intended to have infinitely many solutions. My layout for
>
> Partly true, but in C I use the same layout for "for(;;)" and
> "for(init;cond;next)".
The less I say about your C layout, the better. If I said what I
really think about that, you would probably bar me from the list
for life.The thing is that in C it's the *same* construct:
for (init; test; step) {
body1;
if (test2) break;
body2;
...
}
> I don't see why this isn't the case in Prolog.
> They are equally special and dangerous.
No, I have already explained why they are NOT equally dangerous.
> I must say I find it hard these days to read Prolog without the colouring
> of PceEmacs. Guess its what you're used to. Just keyword colouring isn't
> very valuable in Prolog.
I have seen, I have tried, keyword colouring in Prolog, and find that
it distracts me from the content I need to see and makes the code
nearly unreadable. Prolog has so few keywords, and there is so
little "key" about them, that there seems to me to be practically
nothing gain by highlighting them.
From: Michael Maxwell
Subject: Re: Good Examples of Properly Commented Prolog Code
Richard A. O'Keefe wrote:
> I have seen, I have tried, keyword colouring in Prolog,=20
> and find that it distracts me from the content I need=20
> to see and makes the code nearly unreadable. Prolog=20
> has so few keywords, and there is so little "key" about=20
> them, that there seems to me to be practically nothing=20
> gain by highlighting them.
The times I've been saved by my editor's coloring are times when I forgot to terminate a comment or a string. I don't recall whether this was in Prolog or some other language, but it definitely helped find one class of errors.
From:
Richard O'Keefe
Subject: Re: Good Examples of Properly Commented Prolog Code
Michael Maxwell wrote:
> The times I've been saved by my editor's coloring are times when I =
> forgot to terminate a comment or a string.
The funny thing is that nobody ever seems to cite any other ways that
it helps. There are other and arguably better tools for the job.
For example, my editor includes a "Check Prolog syntax" command
(Meta-_) which checks the syntax of the next clause. It picks up
unterminated comments by noticing /* inside a comment or by having too
long a comment; it can pick up unterminated strings and atoms by
noticing newlines inside strings that are not part of a continuation
sequence. It can *also* find a heck of a lot of other things that
colouring will never notice.
The big thing about syntax colouring is that it tends to restrict you
to having ONE syntax in a file. If you like literate programming
or have examples of one language inside another, you suffer.
From: Richard O'Keefe
Subject: Re: Good Examples of Properly Commented Prolog Code
I wrote:
> Perhaps we could advance this discussion further if Jan provided an
> example where he wanted to use _x _xx and _xxx and see what I can do
> about naming them.
Jan Wielemaker replied:
> I often call the do_something, or whatever. The issue
> frequently arrises on constructs as below. In this particular
> case load_stream/1 would be a good name, but there are enough
> cases where it is much harder to find a good name that isn't the
> same as the name of the main predicate.
>
> load_file(File) :-
> open(File, read, In),
> call_cleanup(do_load_file(In), close(In)).
>
> do_load_file(...)
I've been using Lisp long enough to find the name 'unwind_protect' more
immediately comprehensible than 'call_cleanup', but never mind.
Actually, no. We SHOULD mind. Because if we use the name
'unwind_protect' there is an immediately obvious name for the
subordinate predicate:
% load_file(*File: file_name)
% is given a file name and is responsible for loading it.
% Whatever happens, the stream should not be left open.
load_file(File) :-
open(File, read, In),
unwind_protect(protected_load_stream(In), close(In)).
%. protected_load_stream(*In: input_stream)
% is given a stream open for reading; it loads forms
% from that stream. It is the responsibility of the
% caller to close the stream; this predicate may raise
% exceptions to report problems.
protected_load_stream(In) :-
...
Here there are two changes to the name:
- load_<<file>> becomes load_<<stream>>, because the one is given a file name, and the other is given a stream (which need not ever have had anything to do with a file, so ...load_file... would be a bad name for it.
- <<>>load_stream becomes <<protected_>>load_stream to make it clear that this should only be called in the scope of an unwind_protect/2 that will do whatever cleanup is necessary. If you insist on call_cleanup/2 (which I think is a bad name because it is not the *call* that needs a cleanup action but the *exit, failure, or exception*) then you could turn load_stream<<>> into load_stream<<_no_cleanup>>.
> Properly trained, which unfortunately generally means sticking to the
> style used by the author of the indentation module
Yes, but authors of indentation modules seem to come up with such execrably bad indentation styles.
> or writing your own,
I've tried that too. The thing that I found when I did it is that
RIGID INDENTATION STYLES ARE WRONG. For example, it often pays to
lay things out in a tabular style. Auto-indenters destroy that.
In fact, that's one of the standing complaints about indent(1).
To give a trivial example,
struct {
char *name;
int day;
int month;
} birthday[] = {
{"john", 1, 2},
{"henry", 27, 3},
{"sue", 7, 4},
{"maryanne", 10, 11}
};
is turned into
struct {
char *name;
int day;
int month;
} birthday[] = {
{
"john", 1, 2
},
{
"henry", 27, 3
},
{
"sue", 7, 4
},
{
"maryanne", 10, 11
}
};
which looks horrible and goes out of its way to put the variable name in a bad place.
> auto-indenting editors are very valuable,
I do not call tools that systematically destroy valuable layout clues for the sake of enforcing half a style blindly "valuable".
> as the fact that the
> indentation goes wrong is an immediate clue you've made a mistake.
I repeat an earlier observation: this ONLY works if you are
writing a SINGLE language in a source file and you are NOT using a
literate programming tool. And my experience with Emacs in
several languages has been that the indentation going wrong is almost
always an artefact of Emacs getting it wrong and has nothing to do with
any errors of mine.
Typically, after I've been editing Prolog for about half an hour, I'll
use my editor's built-in Prolog syntax check to look for typos. This
finds the mistakes that auto-indentation might have found (but probably
wouldn't) *and* a lot more (such as singleton variable errors). One
great thing about this is that I can limit the check to the part of a
buffer that *is* Prolog. I can check Prolog clauses embedded in
LaTeX documents or embedded in C comments as easily as I can check
Prolog in files that are nothing but Prolog.
>> The problem with comments like % TBD is that they are still there
>> 4 years and 7 releases later. tbd/1 and list_undefined/0 are excellent
>
> True, but if the problem never became serious who cares?
Someone trying to maintain the program. How do you *know* the problem
never became serious? Maybe the people with the problem just gave up.
Right now I am reading a book about a rather exciting area of
statistics. It was formatted in LaTeX, and *all* the chapter references
in the text are "chapter TEX.NAME" (where TEX.NAME is some internal tag
in capitals and maybe some punctuation) instead of to chapter
numbers. It's a real pain. (Just in case it's not obvious,
the internal tags are not the same as nor are they abbreviations of the
chapter titles). This is a published hardback book.
>> (albeit SWI-specific) advice.
>
> Or any other system with a cross-referencer.
The name of the command is SWI-specific. That's what I meant.
From: Jan Wielemaker
Subject: Re: Good Examples of Properly Commented Prolog Code
Instead of filling this forum, it would be more valuable to enhance the
original style guide ... I've sent a message to Michael who
doesn't appear to be on the mailinglist anymore (or he changed address).
Richard A. O'Keefe wrote:
> I've been using Lisp long enough to find the name 'unwind_protect'
> more immediately comprehensible than 'call_cleanup', but never mind.
> Actually, no. We SHOULD mind. Because if we use the name
> 'unwind_protect' there is an immediately obvious name for the
> subordinate predicate:
call_cleanup/2 was introduced by SICStus (at least thats where I found it first). I prefer to keep the name compatible.
>>load_file(File) :-
>> open(File, read, In),
>> unwind_protect(protected_load_stream(In), close(In)).
>
> Here there are two changes to the name:
>
> (1) load_<<file>> becomes load_<<stream>>, because the one is given a
I already suggested that for this case
> (2) <<>>load_stream becomes <<protected_>>load_stream to make it clear
> that this should only be called in the scope of an unwind_protect/2
> that will do whatever cleanup is necessary.
Actually this isn't correct. You're not going to call write/2
write_protected/2, I may hope. load_stream/1 is a perfectly valid
predicate and like all I/O predicates working on streams doesn't close
the stream itself and is capable of generating errors. Actually
almost any predicate is capable of generating resource errors, and
almost all code needs to be protected at some level against this
possibility.
_protected or whatever is generally a good idea for with_mutex(+Mutex,
:Goal), in which case it is generally not allowed to call the
`protected' code directly and this is not immediately obvious.
>>or writing your own,
>
> I've tried that too. The thing that I found when I did it is that
> RIGID INDENTATION STYLES ARE WRONG. For example, it often pays to
> lay things out in a tabular style. Auto-indenters destroy that.
> In fact, that's one of the standing complaints about indent(1).
Indent is good if you receive code written in a style you don't like at
all and wish to reuse/examine/... it. The result is generally not
perfect, but at least more easy to read than the original. Syntax
support in an editor is different as, while writing, you have the
option to overrule the editor. Indeed, quite a few of the style
rules have exceptions.
>>auto-indenting editors are very valuable,
>
> I do not call tools that systematically destroy valuable layout clues
> for the sake of enforcing half a style blindly "valuable".
It it was `half a style' I'd agree. For Prolog I follow PceEmacs probably about 99% of the time. Ok, I wrote it myself ...
>>as the fact that the
>>indentation goes wrong is an immediate clue you've made a mistake.
>
> I repeat an earlier observation: this ONLY works if you are writing a
> SINGLE language in a source file and you are NOT using a literate
> programming tool. And my experience with Emacs in several languages has
> been that the indentation going wrong is almost always an artefact of Emacs
> getting it wrong and has nothing to do with any errors of mine.
Emacs Prolog mode is pretty poor. Its written by a Lisp programmer :-)
Its not all that difficult to train an editor to recognise multiple
contexts in one file. Of course this only applies to a literal
programming system. The few times I write Prolog code in C files
I can live without indentation support or switch to Prolog mode and
back.
>>> The problem with comments like % TBD is that they are still there
>>> 4 years and 7 releases later. tbd/1 and list_undefined/0 are excellent
>
>> True, but if the problem never became serious who cares?
>
> Someone trying to maintain the program.
He'll generally be more happy with a TBD remark, indicating where and
what the original author thought something should have been done, than
nothing at all.
> How do you *know* the problem never became serious?
> Maybe the people with the problem just gave up.
Thats possible. SWI-Prolog and all its libraries is full of TBD issues.
Most of them never hurt. Quite more often there are issues not flagged
as TBD that cause people to react :-)
From: Jan Wielemaker
Subject: Re: Good Examples of Properly Commented Prolog Code
Jan Wielemaker wrote:
> Instead of filling this forum, it would be more valuable to enhance the
> original style guide ... I've sent a message to Michael who doesn't
> appear to be on the mailinglist anymore (or he changed address).
I've received the LaTeX source from Michael. We are allowed to modify
it, as long as he remains author of course. One plan could be to
incorporate the results of the discussion, add SWI-Prolog specific
concerns and add it as a chapter to the SWI-Prolog manual. Other
plans? Someone willing to invest some time?
From: Roberto Bagnara
Subject: Re: Good Examples of Properly Commented Prolog Code
Jan Wielemaker wrote:
> On Thursday 16 March 2006 17:33, Jan Wielemaker wrote:
>> Instead of filling this forum, it would be more valuable to enhance the
>> original style guide ... I've sent a message to Michael who doesn't
>> appear to be on the mailinglist anymore (or he changed address).
>
> I've received the LaTeX source from Michael. We are allowed to modify
> it, as long as he remains author of course. One plan could be to
> incorporate the results of the discussion, add SWI-Prolog specific
> concerns and add it as a chapter to the SWI-Prolog manual. Other
> plans? Someone willing to invest some time?
I am. Actually, my original plan was to write a document that
could serve as a basis for the current development efforts of my group.
I would have submitted that document to this list for further comments.
Of course, I would have included some of "my personal habits". However,
another possibility is to first improve Michael's manuscript trying to
be as objective as possible: the issues of personal taste can always be
added at a later stage and the intermediate document can be useful to
the community.
From: Bart Demoen
Subject: Re:Good Examples of Properly Commented Prolog Code
> as a chapter to the SWI-Prolog manual. Other
> plans? Someone willing to invest some time?
I would just suggest that one doesn't present "my personal habits" as
general advice to others. There was a lot of that in the recent
"discussion".
From: Jan Wielemaker
Subject: Re: Good Examples of Properly Commented Prolog Code
Bart Demoen wrote:
>> as a chapter to the SWI-Prolog manual. Other
>> plans? Someone willing to invest some time?
>
> I would just suggest that one doesn't present "my personal habits" as
> general advice to others. There was a lot of that in the recent
> "discussion".
I do not agree. Ok, coding style has a personal element. I do see a lot
of poor style and I'm pretty sure it will help if there is good
document to point to that explains what (a) good style is. Even well
written code that is not in _your_ style is generally hard to read,
which make it desirable to share one style in a community. I think a
large majortity of the statements from Michael's document and the
remarks is not just a matter of personal taste.
From:
Simon Price
Subject: Re: Good Examples of Properly Commented Prolog Code
Guidelines are always a mixture of "what's good for everyone" and
"what's good for me" but the hope is that there will be more
convergence on the personal habits side so that it is easier to
read/maintain each others' code. In many cases it doesn't matter what
The Style is so long as people use it. The Java community, for example,
has benefited from a well thought out (mostly) style document that came
out with the language. Editors do have a part to play but are much more
a matter of personal choice or an accident of personal history.
Personally I really appreciate the sharing of ideas on Prolog style -
even the habits.
From: Wlngg
Subject: Re: Good Examples of Properly Commented Prolog Code
... Just to mention one dimension which has been neglected so far in the discussion: the non-English programming world.
With the arrival of Unicode, programming languages (e.g. Java),
following "global" applications (e.g. Word), have made sometimes
considerable efforts to allow localisation.
Could or Should SWI-Prolog follow in the footsteps of Java? May be not
right now. That SWI-Prolog allows now the possibility of programming on
the basis of the Unicode encoding is in itself a great improvement. The
terse syntax of Prolog should only make the main task of adapting the
programming environment to different cultural contexts easier.
Stylistic considerations, while dependent on the cultural context, are
very important for communications purposes. What I would like to see
more of is the increased possibility for programming languages to be
customisable to the specific needs of the cultural and linguistic
contexts of the programming community, or alternatively, to be generic
enough to cater for different cultural and linguistic contexts (the
second alternative being the one I personally favour). For example, LPA
Prolog and even Strawberry Prolog allow predicate names in "any" script
(I have tried it with the first one).
Prolog has a great advantage over languages like Java or C ..., through
its minimalist syntax. Except for built-ins and the few reserved words,
the rest is already pretty international. This should allow for greater
flexibility in Prolog.
To illustrate my point about the need for improvement, take the case of
variables' notation in Prolog : the rule that variables start with
uppercase letters among other (PrologIII takes lower case letters for
variables and upper-case for constants, but a primed upper-case like C'
becomes a variable, etc.) is good for languages that have the
lower/uppercase differentiation; many scripts don't have such a
differentiation. We could do with a more "global" or generic syntax for
variables, and for other aspects of the Prolog language, ... after all,
Prolog is about programming "in logic".
From: Richard O'Keefe
Subject: Re: Good Examples of Properly Commented Prolog Code
Simon Price wrote:
> Guidelines are always a mixture of "what's good for everyone"
> and "what's good for me" but the hope is that there will be more
> convergence on the personal habits side so that it is easier to
> read/maintain each others' code.
The Ada Quality and Style Guidelines are a shining model of how to do
it well. One example of that concerns identifier casing:
Ada is not case sensitive, so tom_paine, Tom_Paine, TOM_PAINE,
toM_painE and so on all mean the same. The first edition of the
AQ&S guidelines had one recommendation. Current editions have
a *different* recommendation, with the remark that experience proved
the original recommendation didn't work as well as they hoped.
It is possible to present style guidelines in a "patterns" sort of way:
- What is the problem?
- What tradeoffs might we have to make?
- What are some alternatives?
- What is the recommendation?
In this case, the specific recommendation might have a "what's good for
me" element, but we may with some reason hope that the rest of the
material remains useful even for people who think they have a better
answer.
> In many cases it doesn't matter what The Style is so long as
> people use it. The Java community, for example, has benefited
> from a well thought out (mostly) style document that came out
> with the language.
Well, no, it *didn't* come out with the language. One of my
students at RMIT developed a style guide for Java *before* the official
one came out. (Versions of Java *were* available before 1.0.)
From:
Richard O'Keefe
Subject: Re: Good Examples of Properly Commented Prolog Code
Wlngg raises the issue of internationalisation.
- Quintus Prolog allowed "any script" back in the late-80s. My proposal to the ISO Committee dealt with this clearly and in some detail; like most of my proposals it was completely ignored.
- I don't know what SWI Prolog does with unquoted identifiers, but it can certainly handle any characters in quoted atoms.
- Concerning variables, the solution which was adopted back in the mid-80s was ever so simple: for scripts that do not have (or, in the case of Georgian, do not use) a lower-case/upper case distinction, variables just begin with "_". This means that the singleton variable check has to be modified slightly:
- do NOT warn about a variable that only occurs once if it begins with "_" followed by an upper case letter or if it begins with "__" followed by a non-case letter
- DO warn about a variable that occurs more than once if it begins with "_" followed by an upper case letter or if it begins with "__" followed by a non-case letter
So _Fred is a named singleton, _(Hanzi) is an ordinary variable where (Hanzi) is a Chinese character, and __(Hanzi) is a named singleton using a Chinese character.
This preserves the rule "stick an underscore in front of a variable name to keep it out of singleton warnings".
Like I said, this is a problem that was solved back in the late 80s.
From:
Wlngg
Subject: Re: Good Examples of Properly Commented Prolog Code
Richard O'Keefe replied:
> (1) Quintus Prolog allowed "any script" back in the late-80s.
> My proposal to the ISO Committee dealt with this clearly and
> in some detail; like most of my proposals it was completely ignored.
With Unicode available now (contrary to the 80s), your proposals should
no longer remain ignored. For some of us they are of an urgent
necessity. In the draft document "An Elementary Prolog Library"
(pllib.htm) Richard O'Keefe wrote the following statement: "In order to
deal effectively with Unicode (and it was the plain responsibility of
the ISO Prolog committee to address this), some other means entirely
will have to be found." If that is still the case, then SWI-Prolog
could provide some of those means without compromising Prolog's
integrity. I think the key principle is that of genericity: if the
satisfaction of a new and specific requirement leads to a greater
genericity of the underlying Prolog syntax, then it should be upheld
I could list a dozen other individual and specific requirements Prolog
could satisfy: programmers with SOV (Subject Object Verb) natural
languages, would wish to be able to write the predicate
is_father_of(abraham,isaac) ...
as
(abraham,isaac)father_of_is ...
further more, in their own script.
Whether or not these new requirements compromise the integrity of
Prolog or entail peformance degradations, it is for the language
designers and compiler writers to decide and accommodate.
In any case, I believe that greater syntactic genericity is the way ahead for Prolog (or any other language for that matter.)
From: Jan Wielemaker
Subject: Re: Good Examples of Properly Commented Prolog Code
Richard A. O'Keefe wrote:
> (2) I don't know what SWI Prolog does with unquoted identifiers,
> but it can certainly handle any characters in quoted atoms.
It follows the rules of Prolog, assuming there are no `singletons'
outside the ASCII range. So, an unquoted atom is a lowercase followed
by a sequence of letter|digit or a sequence of punctuation characters.
A variable is uppercase or _ followed by letter|digit. The
classification is left to iswupper(), etc. of the C-library. Thats not
good, mainly because C libraries differ seriously in how they implement
iswupper() and friends. Notably some only provide meaningful results
for the `installed languages', while others provide support regardless
of installed languages for a smaller or larger subset of the unicode
range. The big problem is that it depends on the C-library and/or
installed languages whether a program can be loaded on another machine.
So, I think I should include tables in Prolog that do the classications
we need and use that in the parser. Its on my todo list. It
isn't a very big issue and it is on my TODO list. Would be good
to have testers willing to provide active feedback, preferable with
some knowledge on coding issues.
From: Bart Demoen
Subject: Re: Good Examples of Properly Commented Prolog Code
> programmers with SOV (Subject Object Verb) natural languages, would=
>
> wish to be able to write the predicate
>
> is_father_of(abraham,isaac) ...
>
> as
>
> (abraham,isaac)father_of_is ...
If you define father_of_is as a postfix operator, you have already (part of) what you want.
But I realise this is an unsatisfactory answer - I myself hate answers
that tell me that "I can do it myself" because I am of course as lazy
as any other person :-)
So I will give you an even more unsatisfactory answer ...
> In any case, I believe that greater syntactic genericity is the way
> ahead for Prolog (or any other language for that matter.)
In the 80-ies, several languages tried the road of "redefinable syntax"
or "user-definable syntax". My first dabbling with Prolog
implementation involved exactly that. That road didn't become the main
language design road - I am not sure why exactly, but the more
flexibility in the syntax, the more difficult it is to let others read
your program. Prolog still allows you to (re)define the operators -
which can make a program totally unreadable to anyone not familiar with
your declarations. Mercury is more rigid in that respect. Cobol was an
attempt at a syntax closer to natural language - that part of Cobol
wasn't that much a success.
If you really want to program in a different syntax, but with an X-like
semantics (X being Prolog at this point I presume), I would say that
you should do your preprocessor yourself, and not try to bend the
X-syntax: there are just too many potential syntax bending ways.
From: Richard O'Keefe
Subject: Re: Good Examples of Properly Commented Prolog Code
I wrote:
> (1) Quintus Prolog allowed "any script" back in the late-80s.
> My proposal to the ISO Committee dealt with this clearly and
> in some detail; like most of my proposals it was completely ignored.
Wlngg wrote:
> With Unicode available now (contrary to the 80s), your proposals should no
> longer remain ignored. For some of us they are of an urgent necessity.
In the 1980s we did not have Unicode, true. But we DID have
- The XNS character set on the Xerox Lisp Machines. (The XNS
character set was a 16-bit character set devised by Xerox for
Network Services; it included Latin, Greek, Cyrillic, and Japanese
characters.)
- Shift-JIS (a quite popular encoding for Japanese)
And there was the ISO 2022 framework as well. So we DID have the
problem of dealing with large character sets (we had a Japanese
terminal and tested the Japanese support in-house). What we DIDN'T have
was a *single* large character set to support.
In fact, one of the earliest documents in the catalogue of documents
distributed to the standardisers, PS/6, already talked about the need
to support large character sets back in 1984 (or maybe '83; I don't
remember when I wrote it).
> I could list a dozen other individual and specific requirements
> Prolog could satisfy: programmers with SOV (Subject Object Verb)
> natural languages, would wish to be able to write the predicate
>
> is_father_of(abraham,isaac) ...
>
> as
> (abraham,isaac)father_of_is ...
>
> further more, in their own script.
I note that Prolog's normal order, Verb Subject Object, does NOT match
the typology of the languages spoken by Prolog's designers (which are
SVO).
There is no question that programmers should be able to use their own
script. I firmly believe that programmers should be able to write
numbers in their own script as well as identifiers (my lexical proposal
to the ISO committee allowed this). In fact, I see no reason why
I shouldn't be allowed to write 2(1/2) using the Latin-1 (1/2)
character.
As for their own syntax, I don't think it is reasonable to ask a
standard to support every variation (all six permutations of SVO,
pro-drop or non-pro-drop, switch reference, ...) that the world's
natural languages exhibit. But there _is_ a case to be made for
allowing people to plug in their own parser. One standard syntax
plus one standard way to plug in your own parser means that someone who
wants (x,y)p can write a parser for that once.
Pluggable parsers for the compiler would of course have no *runtime* performance implications.
From: Wlngg
Subject: Re: Good Examples of Properly Commented Prolog Code
Bart Demoen wrote:
> If you define father_of_is as a postfix operator, you have already
> (part of) what you want.
... is the xyf mode possible in SWI-Prolog?
> In the 80-ies, several languages tried the road of "redefinable
> syntax" or "user-definable syntax". My first dabbling with Prolog
> implementation involved exactly that. That road didn't become the main
> language design road - I am not sure why exactly, but the more
> flexibility in the syntax, the more difficult it is to let others read
> your program. Prolog still allows you to (re)define the operators -
> which can make a program totally unreadable to anyone not familiar
> with your declarations.
Well, in the 80s many people were (rightly) happy with 7-bit encoding.
It was also in the 70s-80s that we had the "Edinburgh"/"Marseilles"
Prologs: PrologIII still offers both styles. Unicode brings with it
many more challenges that will certainly be to the great advantage of
Prolog: its syntax should, ultimately cater for most needs, with the
possibility of reverting any specific form back to the "standard" form
(as it has been possible in the past, to switch from the "Marseilles"
to the "Edinburgh" Prolog and vice-versa). The latter could be
considered an aberration; whereas the first is a necessity: the Arabic
programmers would use a right-to-left style in their own script, as
well as the Hebrew programmers, etc. Each programming community should
have no difficulty communicating among itself, or with the wider Prolog
community. What matters is that they are all programming "in Prolog".
Having said that, I do appreciate the complexities involved, and the
required changes in attitude. All in good time ... For now what is
really urgently required is the ability to program in Prolog, with the
Latin as well as the non-Latin scripts.
Richard O'Keefe wrote:
> There is no question that programmers should be able to use their
> own script. I firmly believe that programmers should be able to
> write numbers in their own script as well as identifiers (my lexical
> proposal to the ISO committee allowed this). [...] As for their own
> syntax, I don't think it is reasonable to ask a standard to support
> every variation (all six permutations of SVO, pro-drop or
> non-pro-drop, switch reference, ...) that the world's natural
> languages exhibit. But there _is_ a case to be made for allowing
> people to plug in their own parser.
Thanks Richard and Bart. I leave you with a greater satisfaction that Prolog is in good hands!
From: Roberto Bagnara
Subject: Re: Good Examples of Properly Commented Prolog Code
Jan Wielemaker wrote:
> Bart Demoen wrote:
>>> as a chapter to the SWI-Prolog manual. Other
>>> plans? Someone willing to invest some time?
>> I would just suggest that one doesn't present "my personal habits" as
>> general advice to others. There was a lot of that in the recent
>> "discussion".
>
> I do not agree. Ok, coding style has a personal element. I do see a lot
> of poor style and I'm pretty sure it will help if there is good document
> to point to that explains what (a) good style is. Even well written code
> that is not in _your_ style is generally hard to read, which make it
> desirable to share one style in a community. I think a large majortity
> of the statements from Michael's document and the remarks is not just a
> matter of personal taste.
Simon Price wrote:
> Guidelines are always a mixture of "what's good for everyone" and
> "what's good for me" but the hope is that there will be more convergence
> on the personal habits side so that it is easier to read/maintain each
> others' code. In many cases it doesn't matter what The Style is so long
> as people use it. The Java community, for example, has benefited from a
> well thought out (mostly) style document that came out with the
> language. Editors do have a part to play but are much more a matter of
> personal choice or an accident of personal history. Personally I really
> appreciate the sharing of ideas on Prolog style - even the habits.
I agree with Jan and Simon. Moreover, as long as the personal
habits come along with their rationale, they can be weighted against
each other and all will help to improve the state of things (which is,
as Jan says, dominated by poor style, starting from my own sources).
Concerning coding guidelines that could have an impact on the
community, I think they should present both the basic and the more
sophisticate techniques. For example, it is clear that
<name>(<mode><var>: <type>, ..., <mode><var>: <type>)
is a very good way to begin the documentation of a predicate.
Especially if one uses the extended set of modes indicated by Richard
(instead of the simpler and less expressive {+, -, ?}). And it is even
better if <type> is specified in a formal way, perhaps in a way
that enables the use of automatic type checkers... However, since the
perfect is the enemy of the good, I believe we should not put things
this way. It is probably more productive to start with a "basic
level" where types can be omitted and where the mode system is the
simpler one and then explain why the more complex mode system is
strictly superior and why specifying types is beneficial. I
believe this way it would be easier to have convergence on the basic
level. If we make sure the more advanced levels are extensions of
the more basic ones and we succeed in explaining why the more advanced
techniques are worth their higher cost, then I believe many people will
adopt the basic level (which is already a nice thing) and, being
conscious of its limitations, some of them will gradually move to more
advanced levels. People working on big project may instead decide
to enforce the more advanced guidelines from the beginning.
From: Paulo Moura
Subject: Re: Good Examples of Properly Commented Prolog Code
Richard A. O'Keefe wrote:
> = (not in Covington) not necessarily ground, but not further bound
> so for example it is simply wrong to write
> compare(?R: order, +T1: term, +T2: term)
> because T1 and T2 are allowed to be variables, and
> compare(?R: order, ?T1: term, ?T2: term)
> is misleading because we expect that ? arguments will be unified
> with something, but
> compare(?R: order, =T1: term, =T2: term)
> is just right.
The Prolog ISO standard (Part I) uses "@" with the same meaning
(section 8.1.2.2; page 64). Thus, compare/3 would be specified as:
compare(?order, @term, @term)
From: Richard O'Keefe
Subject: Re: Good Examples of Properly Commented Prolog Code
Paulo Moura wrote about "extended" modes:
> The Prolog ISO standard (Part I) uses "@" with the same meaning
> (section 8.1.2.2; page 64). Thus, compare/3 would be specified as:
>
> compare(?order, @term, @term)
I keep the ISO Prolog substandard on-line, but I never expect to find
any good ideas in it. The "@" notation has an obvious relationship to
the term comparison predicates @< and so on. That makes '@' arguably
more intention-revealing than '='. Noted.
From: Roberto Bagnara
Subject: Re: Good Examples of Properly Commented Prolog Code
Richard A. O'Keefe wrote:
> Steve Moyle wrote:
>> One place to start would be Covington's guidlines:
>>
>> [PDF] Some Coding Guidelines for Prolog
>> http://www.ai.uga.edu/mc/plcoding.pdf
>
> I have a few quibbles with that.
>
> [...]
>
> 6.6 Test every predicate by forcing it to backtrack.
>
> Misleadingly worded: many predicates *can't* backtrack.
> "Test every predicate by failing back into it"; now that's
> something you *can* do.
I would go a bit further here, since the verb "test" alone does not
mean much. I would give the explicit advice of checking that
failing back into the predicate _really__does_ (as apposed to
_apparently_does_) the right thing. For example, especially (but
not only) when the predicate at hand is meant to be deterministic or
semideterministic, checking that choicepoints left around are as
expected is often the source of interesting surprises. I believe
the paragraph about the importance of mastering the debugger should
explicitly mention this: I met several people (and I am not talking
only about students) that were completely unsuspecting of the
possibility of obtaining a list of pending alternatives.
> 6.7 Test predicates by supplying arguments of the wrong types.
>
> I'm not so sure about this one. For a beginner, it may be useful
> advice so that they find out what is likely to provoke which
> error message. But append/3, for example, is designed to work
> with lists; it isn't *intended* to do anything in particular if
> given arguments of any other type, so there is no sense in which
> you can test it for other types. (To test something, there must
> be a specified set of acceptable behaviours so that you can tell
> whether the actual behaviour is acceptable or not. If "anything
> goes", then the "test" cannot fail.)
Perhaps it means: "When a predicate is part of a public interface, make
sure it fails straightaway when arguments do not satisfy the
requirements set by the interface (instead behaving in a bizarre and
completely unpredictable way." This advice is good for any
language. But I believe for a language such as Prolog is particularly
good: systematic checking of the interfaces has saved me lot of
headaches. My 2 cents, of course.
Noted.
From:
Paul Singleton
Subject: Re: Good Examples of Properly Commented Prolog Code
Roberto Bagnara wrote:
>> 6.6 Test every predicate by forcing it to backtrack.
> ...I would give the explicit advice of checking that
> failing back into the predicate _really__does_ (as apposed to
> _apparently_does_) the right thing. For example, especially (but
> not only) when the predicate at hand is meant to be deterministic
> or semideterministic, checking that choicepoints left around are as
> expected is often the source of interesting surprises.
Is there a reliable programmatic way of checking whether a goal leaves a choicepoint? e.g.
succeeds_without_choicepoint(Goal) :-
statistics(localused, L1),
call(Goal),
statistics(localused, L2),
L1 >= L2.
?- succeeds_without_choicepoint(member(1, [1,2,3])).
No
?- succeeds_without_choicepoint(memberchk(1, [1,2,3])).
Yes
but I guess all sorts of things could go wrong with this?
Nested Predicates?
From: Maurizio Colucci
Whenever
I realize that a predicate is only used inside another one, I miss the
ability to nest predicates. It would make the code more readable:
instead of seeing a flat space, the reader would only see a few
top-level predicates, and make his way into the code in a hierarchical
way, like traversing a tree.
Is there any plan for such an addition to SWI? Is there any standard?
Is there any counter-argument or alternative solution? Thanks
|
From: Paulo Moura
Subject: Re: Nested Predicates?
You may use
SWI-Prolog module system or Logtalk objects to encapsulate auxiliary
predicates and expose only a "few top-level predicates" as you write
above.
From: Jan Wielemaker
To: SWI-Prolog mailing list
Subject: Re: [SWIPL] Nested predicates?
Date: Sun, 26 Mar 2006 17:44:26 +0100
Maurizio Colucci wrote:
> Whenever I realize that a predicate is only used inside another one, I
> miss the ability to nest predicates. It would make the code more
> readable: instead of seeing a flat space, the reader would only see a
> few top-level predicates, and make his way into the code in a
> hierarchical way, like traversing a tree.
>
> Is there any plan for such an addition to SWI? Is there any standard?
I think the answer is 'no no'.
> Is there any counter-argument or alternative solution? Thanks
As suggested, modules (or objects) are the standard way around. As for
counter arguments, I think is needlessly complicates syntax and would
require various new primitives to examine the program, do
meta-interpretation, etc. If there is something to be nested, modules
come to mind as the first candidate. The currently flat module space is
getting a problem as the amount of reusable code grows, especially if
people aren't careful to use some kind of prefix to avoid name-clashes.
From: Richard A. O'Keefe
To: SWI-Prolog mailing list
Subject: Re: [SWIPL] Nested predicates?
Date: Mon, 27 Mar 2006 15:34:55 +1200 (NZST)
Maurizio Colucci wrote:
> Whenever I realize that a predicate is only used inside another one,
> I miss the ability to nest predicates.
When I started writing Prolog code at Edinburgh, I used to indent
auxiliary predicates. Taking the example of calculating Fibonacci
numbers, which happened to come up in another mailing list, I would
have written
:- mode fib(+, ?).
fib(0, 1) :- !.
fib(N, F) :-
integer(N), N > 0,
fib(N, 1, 1, S),
F = S.
:- mode fib(+, +, +, -).
fib(1, X, _, X) :- !.
fib(N, X, Y, S) :-
N1 is N - 1,
W is X + Y,
fib(N1, W, X, S).
Lawrence Byrd looked at some of my code, and said "You really miss Algol, don't you?"
> It would make the code more readable:
Once I got used to the fact that Prolog really isn't that kind of
language, I realised that no, it *didn't* make the code more
readable.
> instead of seeing a flat space, the reader would only see a
> few top-level predicates, and make his way into the code in a
> hierarchical way, like traversing a tree.
This is one of the reasons. Things are like a tree much less
often than one thinks. An auxiliary predicate having been
defined, you often find uses for it elsewhere. (For example, my
fib/3 can also be used to calculate Lucas numbers.)
> Is there any plan for such an addition to SWI?
I hope not.
> Is there any standard?
Yes. The standard is "don't do that".
> Is there any counter-argument or alternative solution?
The readability of your code depends far more on the quality of your
comments than on nesting. I don't really believe that there is
any problem here that needs a solution. The way to encapsulation
stuff is to use modules.
Oddly enough, I've recently been suggesting child modules for another
similar language in another mailing list. Child modules would be
nice for very large systems. Curiously enough, I know of two
languages that *had* hierarchical modules (SETL and Lisp) which dropped
them (ISETL and SETL2 have a flat module name space, as does Common
Lisp).
From: Richard A. O'Keefe
To: SWI-Prolog mailing list
Subject: RE: [SWIPL] Nested predicates?
Date: Tue, 28 Mar 2006 14:07:09 +1200 (NZST)
Ralf Lammel wrote, rather strangely,
> I don't see any convincing argument in Richard O'Keefe's email that
> explains why "nested predicates" in Prolog are *conceptually* less
> sensible than local function definitions in Algol, Haskell, Pascal, ...
That's because I didn't even *try* to provide such an argument, and that's because I did not make any such claim.
I did not say, sing, whistle, hum, inscribe on clay, stone, sand, wax,
or any other material, transmit by Morse code, mental telepathy,
pheromones, or any other means, nor cause nor induce any other person
to do so, any statement which could be held as meaning or implying that
"nested predicates are conceptually less sensible in Prolog".
They aren't *necessary*.
They would greatly complicate the language.
They would greatly increase the difficulty of writing tools to process the language.
Using Prolog since October 1979 has shown me that they would have VERY low payoff.
But none of that says that they are "conceptually less sensible".
Of *course* you could have a logic programming language with nested
predicates, and if you want one, by all means go ahead and design and
implement it. Just don't expect anyone else to pay the price.
> Richard says that "Things are like a tree much less often than
> one thinks." but how does this explain that local definitions
> are used quite a bit in say Haskell
As it happens, between the time I wrote this sentence and the time I'll
write the next one, I'm going to give a 2 hour lecture on Haskell
programming [left at 10:54am]. [returned at 1:06pm] Can I make the
obvious point that "Things are like a tree much less often than one
thinks" was a statement about Prolog, so cannot fairly be expected to
explain anything about Haskell?
Let me also make the obvious point that Haskell *needs* local
definitions because it doesn't have unification. Consider the
following Haskell code for computing the "percentage bend correlation":
pbcor = pbcor_gen 0.1
pbcor_gen beta pairs =
dot as bs / sqrt (dot as as * dot bs bs)
where
dot xs ys = sum [x*y | (x,y) <- zip xs ys]
as = scaled_and_clamped xs
bs = scaled_and_clamped ys
(xs, ys) = unzip pairs
scaled_and_clamped xs =
[(0-1) `max` (1 `min` ((x-phi)/omega)) | x <- xs]
where
phi = (omega*fromIntegral (i2-i1) + s)/fromIntegral (n-i1-i2)
s = sum ((n-i1-i2) `take` (i1 `drop` sorted_xs))
sorted_xs = sort xs
i1 = length [1 | x <- xs, (x-median)/omega < -1]
i2 = length [1 | x <- xs, (x-median)/omega > 1]
omega = sort ws !! (floor ((1 - beta)*fromIntegral n) - 1)
ws = [abs (x - median) | x <- xs]
median = (sorted_xs !! h + sorted_xs !! (n-1-h)) / 2
h = n `div` 2
n = length xs
In this Haskell code, there are
2 nested functions
14 nested variables
5 list comprehensions
What does a Haskell compiler do with the nested functions? IT MOVES
THEM OUT! (This is called 'lambda lifting', IIRC.) We can do the
same for Prolog.
pbcor(Pairs, R) :-
pbcor(Pairs, 0.1, R).
pbcor(Pairs, Beta, R) :-
unzip(Pairs, Xs, Ys), % no let/where is needed for
scaled_and_clamped(Xs, Beta, As), % introducing Xs, Ys, As, or Bs.
scaled_and_clapmed(Ys, Beta, Bs), % (nor AA, AB, or BB).
dot(As, As, AA),
dot(As, Bs, AB),
dot(Bs, Bs, BB),
R is AB/sqrt(AA*BB).
scaled_and_clamped(Xs, Beta, Ys) :-
length(Xs, N), % once again, there is no need
H1 is N // 2, % for any let/where just so that
H2 is N - 1 - H1, % we can define local variables!
msort(Xs, Sorted_Xs),
nth0(H1, Sorted_Xs, Mid_Lo),
nth0(H2, Sorted_Xs, Mid_Hi),
Median is (Mid_Lo + Mid_Hi)/2,
Median_Shift is -Median,
scaled_and_shifted(Xs, 1, Median_Shift, Ms),
abs_list(Ms, Ws),
msort(Ws, Sorted_Ws),
Omega_Pos is floor((1 - Beta)*N) - 1,
nth0(Omega_Pos, Sorted_Ws, Omega),
count(X, Ms, X < -Omega, I1),
count(X, Ms, X > Omega, I2),
I3 is N - I1 - I2,
drop(I1, Sorted_Xs, Mid_And_High_Xs),
take(I3, Mid_And_High_Xs, Mid_Xs),
sum(Mid_Xs, S),
Phi is (Omega*(I2-I1) + S)/I3,
Scale is 1.0/Omega,
Shift is -Phi/Omega,
scale_and_shift(Xs, Scale, Shift, Scaled_And_Shifted),
clamp(Scaled_And_Shifted, -1, 1, Ys).
/* Library predicates */
/* All of these are or should be in your Prolog library,
possibly with a different name and interface,
except for scale_and_shift/4, clamp/4, abs_list/2.
I'm a bit embarrassed about those, to tell you the
truth, but NONE of these functions is coupled to its
uses in the preceding code; ALL are reusable.
*/
unzip([], [], []).
unzip([(X,Y)|Pairs], [X|Xs], [Y|Ys]) :-
unzip(Pairs, Xs, Ys).
dot(Xs, Ys, Dot) :-
dot(Xs, Ys, 0, Dot).
% NB: at first sight, dot/4 should be private to dot/3,
% but I have *VERY* often had a use for calling it directly
% instead of calling dot/3.
dot([], [], Dot, Dot).
dot([X|Xs], [Y|Ys], Dot0, Dot) :-
Dot1 is Dot0 + X*Y,
dot(Xs, Ys, Dot1, Dot).
sum(Xs, Sum) :-
sum(Xs, 0, Sum).
% NB: at first sight, sum/3 should be private to sum/2,
% but again, I find it useful to call sum/3 directly about
% as often as it's useful to call sum/2.
sum([], Sum, Sum).
sum([X|Xs], Sum0, Sum) :-
Sum1 is Sum0 + X,
sum(Xs, Sum1, Sum).
scale_and_shift([], _, _, []).
scale_and_shift([X|Xs], A, B, [Y|Ys]) :-
Y is A*X+B,
scale_and_sfhit(Xs, A, B, Ys).
clamp([], _, _, []).
clamp([X|Xs], L, U, [Y|Ys]) :-
( X < L -> Y = L
; X > U -> Y = U
; Y = X
),
clamp(Xs, L, U, Ys).
count(Template, List, Condition, Count) :-
findall(*, ( member(Template, List), Condition ), L),
length(L, Count).
abs_list([], []).
abs_list([X|Xs], [Y|Ys]) :-
Y is abs(X),
abs_list(Xs, Ys).
drop(N, [_|Xs], Ys) :- N > 0, !,
N1 is N - 1,
drop(N1, Xs, Ys).
drop(_, Xs, Xs).
take(N, [X|Xs], [X|Ys]) :- N > 0, !,
N1 is N - 1,
take(N1, Xs, Ys).
take(0, _, []) :- !.
take(_, [], []).
nth0(N, List, X) :-
( integer(N) ->
N >= 0,
nth0i(N, List, X)
; var(X) ->
nth0v(L, X, 0, N)
; abort
).
nth0v([X|_], X, N, N).
nth0v([_|Xs], X, N0, N) :-
N1 is N0 + 1,
nth0v(Xs, X, N1, N).
nth0i(N, {X|Xs], V) :-
( N =:= 0 -> V = X
; N1 is N - 1,
nth0i(N1, Xs, V)
).
Oh, let's clean that up a bit. The preferred way to take a section of a
list in Prolog is not to use take and drop but to use length:sublist.
drop(I1, Sorted_Xs, Mid_And_High_Xs),
take(I3, Mid_And_High_Xs, Mid_Xs),
should be
length:sublist(Sorted_Xs, Mid_Xs, I1, I3, I2)
We can do list comprehensions with findall/3:
Median_Shift is -Median,
scaled_and_shifted(Xs, 1, Median_Shift, Ms),
abs_list(Ms, Ws),
could be
findall(W, (member(X, Xs), W is abs(X-Median)), Ws)
and
Scale is 1.0/Omega,
Shift is -Phi/Omega,
scale_and_shift(Xs, Scale, Shift, Scaled_And_Shifted),
clamp(Scaled_And_Shifted, -1, 1, Ys).
could be
findall(Y, ( member(X, Xs), Y is min(1, max(-1, (X-Phi)/Omega)) ), Ys)
I just wish findall/3 were as cheap as "map", but it isn't.
> but they wouldn't be used in Prolog, if they were enabled?
I dare say they WOULD be used in Prolog. *BUT* "enabling" nested
definitions in Prolog would not be a simple matter of flipping a switch
(as "enabled" suggests), but of fairly major design and implementation
work. And the compiler would have to implement them the way a Haskell
compiler does: by turning them into UN-nested functions.
> I could try to guess that this
> may have something to do with the more pervasive higher-order
> style in Haskell, but I would think that people also use
> "where"s in first-order Haskell programs.
Of course they do. They use 'where' a LOT in Haskell, for the
simple reason that if you want to name an intermediate result (that is
NOT a function) you have to use "let" or "where".
> I don't have metrics results that back up my guess. I could
> come up with some other guesses; I have indeed some candidates,
> but perhaps someone *knows*.
Take a look at Mercury. It combines logic programming and
functional programming, and it is quite good at supporting higher order
programming, and it HAS anonymous nested function and predicate
definitions (tamed by the mode system so that higher-order unification
is never needed).
In short, if you don't like Prolog, try Mercury. It may be
exactly what you want. (And it requires a very complex
implementation, but someone has already done that.)
> In fact, the (first-order) fib function appears to me as a reasonable
> example of a function that benefits from a local helper. Richard says
> "An auxiliary predicate having been defined, you often find uses for it
> elsewhere. (For example, my fib/3 can also be used to calculate Lucas
> numbers.)" I get the point for fib/3 but the general argument seems to
> lead to a slippery slope because it sounds a bit like let's presume most
> abstractions are reusable in perhaps unanticipated ways, so better
> expose them, so better export almost everything.
No, I did *NOT* say "export". In fact I said the explicit
opposite of that. I said to ENCAPSULATE functions that would have
been nested, but to encapsulate them using MODULES.
In fact the example I presented above is a good (because entirely
honest) demonstration of my point: with the single exception of
scaled_and_clamped/3, every single auxiliary predicate I needed either
*was* in my library already or could profitably be put there.
Ones that weren't already in the library would not be *EXPORTED*; they
would be kept at top level until they were needed in another module at
which point they would be moved out of this module entirely and into an
appropriate module in the library.
> Below, I list some ways to transcribe O'Keefe's fib to Haskell. The
> first one is the more sensible one for the case of fib; it associates
> the helper with the relevant equation. The other two variations
> illustrate notational options -- one can make it so that a helper is
> accessible by several "cases". In reality, one uses local functions for
> more than just "hiding" or "grouping with a client", i.e., they are used
> as means to take advantage of the existing argument bindings for the
> parent. The fib example cannot make interesting use of this possibility
> but here is a lambda-dropped version of append that binds ys at the top:
>
> append xs ys = append xs
> where
> append [] = ys
> append (x:xs) = x : append xs
And do you know what a Haskell compiler does with this? It turns it into
append xs ys = (append' ys) xs
append' ys [] = ys
append' ys (x:xs) = x : (append' ys) xs
which is just the original definition twisted around a bit. One has to
ask, what *is* the point? Why move something in when it's going
to be moved straight out again?
In fact there is a specific feature of Haskell which means that nested
functions are often better as un-nested functions (with export being
controlled in Haskell, as in SWI Prolog, by the module system). That's
the well known scope-of-type-variables problem.
> -- Attach the helper to the relevant equation
> fib 0 = 1
> fib n = fib n 1 1
> where fib 1 x _ = x
> fib n x y = fib (n-1) (x+y) x
Which the Haskell compiler turns into
fib 0 = 1
fib n = fib' n 1 1
fib' 1 x _ = x
fib' n x y = fib' (n-1) (x+y) x
The nested function is HARDER to read because the human reader has to
ask "WHY is this function nested? HOW is it coupled to its
surroundings?" In this case, the answer turns out to be "it isn't",
which causes a justified feeling of resentment: "why, Mr Author, did
you make me do all that decoding work for nothing?"
> -- Factor for a single binding, multiple RHSs
> fib n | n == 0 = 1
> | otherwise = fib n 1 1
> where fib 1 x _ = x
> fib n x y = fib (n-1) (x+y) x
This has all the disadvantages of the previous version, plus some
scoping weirdness: 'where' definitions are available in multiple
alternatives for a single rule, but not for multiple rules.
Again, this is far more readable if the nested function is NOT nested.
Just to try, possibly vainly, to avoid any misconception: I am NOT
saying that nested functions are undesirable or inappropriate or
anything like that in Haskell. What I *am* saying is that if an
auxiliary function isn't *coupled* to its context through one or more
shared variables, it is almost always better to un-nest it. (I
wrote the pbcor function at the top, and the only reason dot is
declared inside is sheer laziness; I am sorry I did that.)
From: Bart Demoen
To: SWI-Prolog mailing list
Subject: Re: [SWIPL] Nested predicates?
Date: Tue, 28 Mar 2006 21:47:39 +0200
Richard A. O'Keefe wrote:
> if an auxiliary function isn't *coupled* to its context through one
> or more shared variables, it is almost always better to un-nest it.
> ...
> the only reason dot is declared inside is sheer laziness
[I didn't read all of Richard's mail]
I find the ability to nest functions/predicates attractive because I
can often vouch for "this definition is correct when used as in the
context just surrounding it" but not for any larger context. That's
lazyness, or lack of time, but most of all it is trying to err on the
safe side: don't make things public that you're not prepared to
"maintain".
"*coupled* to its context through one or more shared variables" is one
aspect of coupling to a context - it couls also be because of
preconditions that are context dependent.
From: Richard A. O'Keefe
To: SWI-Prolog mailing list
Subject: Re: [SWIPL] Nested predicates?
Date: Wed, 29 Mar 2006 16:51:25 +1200 (NZST)
I wrote:
>> if an auxiliary function isn't *coupled* to its context through one
>> or more shared variables, it is almost always better to un-nest it.
Bart Demoen replied:
> I find the ability to nest functions/predicates attractive because I
> can often vouch for "this definition is correct when used as in the
> context just surrounding it" but not for any larger context.
This brings us back to the discussion about style that we were having a
while back. What is to the advantage of the *writer* of a piece
of code is not always to the advantage of the *reader*.
To me the important question is "how will someone ELSE, reading this code, KNOW what the relevant context actually is?"
> That's lazyness, or lack of time, but most of all it is trying to err
> on the safe side: don't make things public that you're not prepared to
> "maintain".
This is a straw man. Consider this table:
|
public |
not public |
nested |
possible |
possible |
not nested |
possible |
possible |
I have been, and remain, concerned solely with the nested/not nested
question. This is entirely orthogonal to the public/not public
question. All four combinations are theoretically possible and all four
of them are supported in some programming language or other.
(Lisp, for instance.)
Making something "not nested" is very very VERY different from making it public.
> "*coupled* to its context through one or more shared variables" is one
> aspect of coupling to a context - it could also be because of
> preconditions that are context dependent.
Right. Which is why simply putting one routine inside another one
is NOT enough all by itself. A future reader (including yourself
a few months later) needs to know WHAT the relevant context is.
That means commenting it.
Now, take an example from my earlier message:
nth0(N, List, X) :-
( integer(N) ->
N >= 0,
nth0i(N, List, X)
; var(X) ->
nth0v(L, X, 0, N)
; abort
).
nth0v([X|_], X, N, N).
nth0v([_|Xs], X, N0, N) :-
N1 is N0 + 1,
nth0v(Xs, X, N1, N).
nth0i(N, {X|Xs], V) :-
( N =:= 0 -> V = X
; N1 is N - 1,
nth0i(N1, Xs, V)
).
nth0/3 is exported from the module it is defined in. It is "public" and "not nested".
nth0v/4 and nth0i/3 are *not* exported from that module. They are "not public" and "not nested".
Making them "not nested" means a big benefit for a human reader:
whatever the context might be (if any) that links them to nth0/3, it
*isn't* shared variables. When I am trying to understand a
variable in nth0i/3, I do not have to look anywhere else at all.
There _is_ a coupling between these predicates and their caller, and in
the real code, it's explained in the comments I stripped out to keep
the previous message short:
%. nth0v(?List: list(T), ?Element: T, +N0: integer, -N: integer).
%. nth0i(?List: list(T), ?Element: T, +I: integer).
So, would this be better if we made these predicates nested?
nth0(N, List, X) :-
( integer(N) ->
N >= 0,
nth0i(N, List, X)
where {|
nth0i(N, {X|Xs], V) :-
( N =:= 0 -> V = X
; N1 is N - 1,
nth0i(N1, Xs, V)
)
|}
; var(X) ->
nth0v(L, X, 0, N)
where {|
nth0v([X|_], X, N, N);;
nth0v([_|Xs], X, N0, N) :-
N1 is N0 + 1,
nth0v(Xs, X, N1, N)
|}
; abort
).
I don't think so. We run into a prlblem: are N and X the
same thing inside nth0i/3 as they are in nth0/3, and if not, why
not? The sheer bulk makes this version of nth0/3 hard to read.
But most tellingly, there is *also* in that library a predicate
nth1(N, L, X) :-
( integer(N) ->
N >= 1,
M is N - 1,
nth0i(M, L, X)
; var(N) ->
nth0v(L, X, 1, N)
; abort
).
Yes, those predicates depended on instantiation state guaranteed by the
caller, BUT another caller within the same module could make the same
guarantees.
If routines are unnested ("lambda-lifted") and the context conditions are made EXPLICIT, then
- the routines, though still private, are available for further use within the same module
- whether
they are used again or not, the life of human readers is made
vastly simpler because they are TOLD what they would
otherwise have to puzzle out.
Make no mistake.
I have known and loved Algol-like languages for a long time. (The
second non-trivial program I ever worked on was a port of Wirth's Euler
compiler.) I have known and loved Lisp-like languages for a long time.
I regard the lack of nesting functions in C++ as a serious flaw. Give
me a language that allows nested definitions, and I will use them.
I'm not arguing that nested definitions are a bad idea.
I'm making two claims:
- *implicit* coupling is not good.
- One of the
main advantages of Prolog is its simplicity; adding nested
definitions to Prolog would make it a much more complicated
language, and empirically, the benefit would not be great.
As I've said before, if I want Mercury, I know where to find it. (~/export/mercury.d/, as it happens.)