Software As Read

Software As Read

By Jon Jagger

Overload, 11(57):, October 2003


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::string s 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.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.