ACCU Home page ACCU Conference Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Google+ ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinSoftware As Read

Overload Journal #57 - Oct 2003 + Programming Topics   Author: Jon Jagger

Programming is writing, and writing is visual. We should explore software as read not code as executed. Less code, more software.

Iteration

In his Overload 45 (October 2001) article, minimalism - omit needless code, Kevlin worked on the simple problem of printing the std::strings inside a std::vector to std::cout. An early version looked like this:

typedef vector<string> strings;
typedef strings::iterator iterator;
for (iterator at = items.begin();
at != items.end(); ++at) {
  cout << *at << endl;
}

A later version looked like this:

class print { ... };
for_each(items.begin(), items.end(),
         print(cout));

And the final version looked like this:

typedef ostream_iterator<string> out;
copy(items.begin(), items.end(),
     out(cout, "\n"));

Readability

The main source of repetition is repetition. When programming in C++ you often find yourself making a call to a template function where two of the arguments are created by calling begin and end on a container. This quickly gets repetitive. The repetition itself suggests several solutions. Ranges are basic building blocks of the STL design and it is surprising they are not a visible and explicit artefact of its type system. For example:

template<typename iterator>
class range {
public: // types
  typedef iterator iterator;
public: // c'tor
  range(iterator start, iterator finish)
    : from(start), until(finish) { }
public: // properties
  iterator begin() const {
    return from;
  }
  iterator end() const {
    return until;
  }
private: // state
  iterator from, until;
};

This would make containers substitutable for a range over themselves which would in turn allow STL algorithms to expect a range argument rather than two iterator arguments. For example:

template<typename range,
         typename function>
function for_each(const range & all,
                  function apply) {
  return for_each(all.begin(), all.end(),
                  apply);
}

This version of for_each is not part of STL so you have to provide it yourself. Once you've done this you can rewrite this:

for_each(items.begin(), items.end(),
         print(cout));

as the impressively readable:

for_each(items, print(cout));

Understanding this statement is a complete no brainer. It clearly and concisely expresses its intention. However, it does require you to create the print class (which hides away the "\n" detail). Alternatively, you could pull the same trick by writing a non standard version of copy:

template<typename range, typename output>
output copy(const range & source,
            output sink) {
  return copy(source.begin(),
              source.end(), sink);
}

allowing the beautifully readable:

copy(items, out(cout, "\n"));

Preference

Which versions do you prefer? The explicit iteration, the for_each versions, or the copy versions? Can you explain why?

I prefer the copy versions. The name for_each is itself a subtle but strong hint that iteration is involved. It suggests that each of the items will be printed to std::cout, one at a time. The iteration comes first (for_each, leftmost), followed by the action (print, rightmost). In contrast, the copy is subtler and simply suggests copying the items to cout. It has more of a "single operation" feel to it. The iteration is not visible (and the "\n" is). This difference is important, not because you should always try to hide all iteration, but because the intention was to "write the items to cout". In other words, the copy version is a simpler and more direct expression of the problem. Lots of code is too solution focused; it lacks an expression of the problem and hence is hard to understand and costly to maintain.

Many thanks to Kevlin for an insightful review of a first draft of this article.

Overload Journal #57 - Oct 2003 + Programming Topics