A couple of loop examples
(This entry originated as a post on the EiffelStudio user group mailing list.)
Here are a couple of actual examples of the new loop variants discussed in the blog entry immediately preceding this one. They came out of my current work; I started updating a program to take advantage of the new facility.
As a typical example, I replaced
local
eht: HASH_TABLE [EXPRESSION, EXPRESSION]
do
…
from
eht := item (e)
eht.start
until
eht.off
loop
Result.extend (eht.key_for_iteration)
eht.forth
end
by
across item (e) as eht loop Result.extend (eht.key) end
which also gets rid of the local variable declaration. The second form is syntactic sugar for the first, but I find it justified.
Another case, involving nested loops:
— Previously:
from
other.start
until
other.off
loop
oht := other.item_for_iteration
e := other.key_for_iteration
from
oht.start
until
oht.off
loop
put (e, oht.item_for_iteration)
oht.forth
end
other.forth
end
— Now:
across other as o loop
across o.item as oht loop put (o.key, oht.item) end
end
here getting rid of two local variable declarations (although I might for efficiency reintroduce the variable e to compute o.key just once).
It is important to note that these are not your grandmother’s typical loops: they iterate on complex data structures, specifically hash tables where the keys are lists and the items are themselves hash tables, with lists as both items and keys.
The mechanism is applicable to all the relevant data structures in EiffelBase (in other words, no need for the programmer to modify anything, just apply the across loop to any such structure), and can easily extended to any new structure that one wishes to define. In the short term at least, I still plan in my introductory teaching to show the explicit variants first, as it is important for beginners to understand how a loop works. (My hunch based on previous cases is that after a while we will feel comfortable introducing the abstract variants from the start, but it takes some time to learn how to do it right.)
Is it April 1. in Switzerland or in the orthodox calendar or something?
This proposal is so embarrassing that I don’t know where to start.
– What happened to the minimalism principle? Eiffel used to have one good way of doing each thing, for clarity and homogeneity and common understanding in the context of team work, and it’s well worth the modest cost of the odd slightly painful border cases. We now have not one but 3 constructs (legacy loops, which I’ll call neanderthal loops in the rest of this comment; across loops, let’s call them neo-neanderthal loops; and agent-based iterators, the one you use in modern Eiffel). And then you can multiply by the iterator variants.
– The argument that loops in their most general form are an everyday programming construct does not apply. Loops in regular programming are all from a very small family of very regular loops, usually on containers or similar like INTEGER_INTERVAL, that are very well captured by iteration agents (do_all and friends). A full time Eiffel programmer would probably need to write an explicit loop once a year or less. The very rare cases I meet where I have to write a neanderthal loop is because of omissions in libraries who sometimes miss some obvious looping construct (almost always general enough, I’m not advocating adding convoluted do_xx to containers that apply only to to one specifically). Lazy readers might think “but I write loops all the time”, but if you look at all the loops you’ve written and think abstractly. Neanderthal-loop writers often tell me that some loops can’t be written with a generic agent iterator but when I ask them to show me an example all they can find is example of where they were being lazy or used a sadly incomplete library (I don’t think anyone who promised me an example _ever_ delivered a credible one, though I’m not saying a few cases don’t exist, they’re just very rare). The great advantage of using agent-based iterators is that their behaviour (termination etc) is much easier to reason about, by hand or machine. If your system has a handful of generic loops (in the implementation of the container library) it’s much easier to reason about once you’ve proven your library.
– The use of a keyword for library syntaxic sugar is upside down. Core language constructs should have minimal dependencies if any at all on upper level libraries in general, and individual routine/classes fairly down the abstraction chain at that. What next, syntactic sugar for log routines?
– The verbosity argument is so anti-Eiffel as well, Eiffel is about clarity not about line count. If we’re going that way we could as well go punctuation {*o:i|print(i)*}. With a bit of effort the gold standard of syntax mediocrity, perl, could be without our reach!
– The verbosity comparison between fully expanded neanderthal loops and the neo-neanderthal variant in the posts is disingenuous at best, the fair comparison would be for example:
from c := list.new_cursor until c.after loop
print (c.item)
c.forth
end
which is a style used by leading practitioners and the one consistent with the style of the neo-neanderthal examples.
– If we do use keywords, it creates keyword pollution, and while keywords shouldn’t be overloaded, they should neither be created for totally spurious reasons for things that belong to libraries.
– If I was trying to add a loop construct of that genre, I’d hide the cursor totally and do:
across item in container loop print (item) end
which is what other languages with neo-neanderthal looping constructs do, for a good reason. We don’t need to be gratuitously worse than the common practice for the sake of originality.
– The construct above could be generalised to the hash table case with something like ‘across item, key in table loop’ — were it not anyway totally superfluous given that do_all_with_key does it much better (the only reason it’s not in HASH_TABLE is that as far as I know the maintainer got arrested for earlier crimes against software quality — the class is a crime hotspot — so the class has been unmaintained since the lengthy court martial proceedings started). Low-crime hash table classes like the one in Gobo are well equipped.
I’ve probably forgotten other weaknesses of the proposal but that’ll be enough for today.
To finish I’ll expose my theory why this proposal happened: one of the fundamental laws of the universe is the Good Idea Entropy Axiom, which reads:
– The aggregate quality of ideas available in the universe must never increase.
and I think that Bertrand has taken this theorem individually and to compensate for the excellent theory of aliasing has had to introduce across so that his net contribution to the world of ideas is not positive. But the universe is full of people who produce more than their fair shares of bad ideas, so to each to their ability: I urge Bertrand leave to others the jobs of creating junk language proposals and keeps to the supply of good ideas.
Sorry for not approving this comment from January — for some reason WordPress was not showing it to me (or I was not seeing it) until now.
By “approve” I don’t mean that I agree, of course (but I hope you didn’t think I was trying to censor your critique).
By using emotionally charged language you make it hard to answer factually; let me simply note that even the simple loop you propose can be significantly simplified:
across list as l loop print (l.item) end
which I think reads better. You also do not say how you would write loops representing predicates, such as:
across list as l all l.item > 0 end
which we have started to use extensively in contracts.
Thanks for the compliment, much appreciated, on the alias analysis work.
— BM
I like the idea of loops as expressions, allowing a declarative compositional style of programming.
I wonder what your view is on invariant data structures and LINQ style programming where you would write: list.All(item => item > 0) – unfortunately we still cannot write list.All(> 0) in a language such as C#. There are quite efficient invariant data structures like finger trees and it would appear natural in a concurrent world that we use invariance to protect ourselves from working on quicksand and invariant data structures also go like hand-in-glove with declarative query-style programming and is very easy to reason about.
Thoughts? :-)
Less appropos, I use contracts everywhere in my C# programs but since there is no syntactic support for them, they look much more ugly than Eiffel contracts.
[…] about this point in a comment to this post. The new across loop variant described in two later postings uses external cursors and manages them automatically, so this business of maintaining the cursor […]