Auto-Increment Proposal

Revision as of 04:19, 9 March 2007 by Rosmond (Talk | contribs) (Enhancing the Loop Construct)

Author: Roger F. Osmond

An Auto-Increment/Decrement Mechanism for Eiffel Integers

In my never-ending quest to remove barriers standing in the way of Eiffel adoption by the masses, I was having a think on the oh-so-popular auto-increment and auto-decrement features of C and its kin (e.g. i++, i--).

You have to admit that it's a tidy notation, albeit the epitome of side-effect worship. Still there is merit in looking at the "Why" that makes it so popular. As I see it, the most common usage, and where Eiffel gets dinged most often, is when an integer is being used as a loop counter or index. Eiffel has but one loop construct, and it is good. C has a dog-pile of them, but folks do seem to like them. Why?

I think Eiffel can and should live with a single loop. It really is a good thing, but you have to admit it's a pain in various extremities to keep doing the old "i := i + 1". Sure, we can user iterators, but that's just a confusing mess for newbies, and it really doesn't address the fundamental objection from non-Eiffelists.

They simply want a simpler way to increment that stupid little local integer entity. So why not give them what they (and I dare say quite a few of "us") want?

Let' break it down first.

The construct in question is the ubiquitous "i++" (and its twin, i--). It has two personalities. First, it quickly and easily increments (decrements) an entity. Fair enough. It _can_ also be referenced as a value. This is of course a mortal sin in the Church of Eiffel (as it should be).

To be fair, the first personality is pretty handy, and altogether defensible, even by Eiffel standards. The second personality is of course its mister Hyde.

I bet if we could give them the first part, then missing the second part wouldn't be such a big deal.

Let's have a whack at it.

If we were (get ready; you might think I'm on drugs or something) to look at INTEGER as more like a data structure than we have in the past (or at least something more object-like), then an interesting option presents.

What if we were to define a feature in INTEGER_REF (and its various flavors) called "forth" (and its sister, called "back")? Then, we could do something like "i.forth" instead of i := i + 1; The especially numerate among you might have noticed that there are no keystrokes saved, but bear with me. Still, we get two procedures that increment and decrement an INTEGER, complete with postconditions. (the same mechanism would work nicely for CHARACTERs, where forth would increase the code by one) If we then leverage the alias mechanism, we can substitute a punctuation of sorts for the forth and back calls. This would be just like the "+" alias for "append", and I hope would not offend anyone.

There is a wrinkle. Eiffel presently does not support a postfix alias. Postfix is the obvious choice here. It also, at least as far as I know, doesn't support unary aliasing. I understand from personal experience that unary operators are a pain, but they're a reality. A double-dip operator, like the famous "++" I think would help in the ambiguity department. It has been suggested that perhaps using the prefix form would be a reasonable alternative. If the postfix form presents too much of a challenge, then the prefix form should be adequate.

With these relatively minor changes, we would have removed another point of discomfort for potential converts, and without compromising any Eiffel principles. In fact, this interpretation of "++" would be an excellent example to other language folks of how to do it.

The Eiffel version would not have a value like the C version, and therefore would be side-effect free. Sure, the C version was designed to have a side effect and many C practitioners depend on that behavior in the for loop controls. Eiffel doesn't have a for loop. It also doesn't have a while loop. It all seems quite self-consistent to me.

Enhancing the Loop Construct

While we're on the topic, let's have a look at the loop construct itself. Eiffel has one form and as I said before, that's a good thing. There are sub-forms however. For example, loop assertions are optional. Including or omitting these results in different forms. If we were to introduce another optional component to the loop - an iterator expression, I think the loop would be better specified and easier to understand. Here is an example (note the use of the forth feature of INTEGER)

from i := 1
  until i > 5
  by i.forth
  loop
    {loop body}
  end

The 'by' keyword introduces the iterator statement. In this case, it's a simple procedure call to i.forth. It can as easily be any statement, even an agent. I think this latter case opens up the possibility of creating a much more usable iterator class by the way.

Because the iterator statement is optional, there is no issue of backward compatibility. All existing (and future) loops without the iterator statement would be just fine. An alternate form would have the iterator statement at the bottom of the loop.

from i := 1
  until i > 5
  loop
    {loop body}
  by i.forth
  end

While this more closely resembles the way code is written by hand, to my thinking it has much less value because it splits the loop specification into 2 sections, possibly widely separated. I much prefer the spec being all together at the top. It just reads better as a spec, and I think it would encourage forethought - resulting in more reliable loops. Leaving the iterator to the end is just asking for incomplete thinking.

Another option might be to drop the "by" keyword altogether. The presence of a legitimate statement itself might be adequate. The construct then would look like this.

from i := 1;
  until i > 5
  i++;
  loop
    (loop body)
  end;

It seems to me that there could be ambiguity issues, but I'll leave that to the experts. It looks pretty tidy to me and it resembles the C for loop enough to make converts feel right at home, without sacrificing Eiffel's ethics.

How does all this impact loop-level assertions? I think there is potential to have a very positive impact. At present, loop assertions are a mystery to most. I use them, but not all the time (shame on me).

Newbies simply freak out, especially when confronted with the rules for loop variants. By including the iterator statement in the loop spec, it should be possible to generate loop variants automatically.

Loop invariants remain as before. This could help quite a bit.

We could have another switch in the system configuration to enable automatic loop variant assertions. They would exist only for those loops with a complete specification - i.e. with an iterator expression.

Those folks who use loops and indices the most I think would welcome this feature and the forth/++ extension to integer.