
The Wrong Aesthetic - urlwolf
http://sheddingbikes.com/posts/1276445247.html
======
axiom
I've always felt that most good programmers go through 3 stages.

Stage 1: Write very simple, naive code, no fancy design patterns, just kind of
brute force everything.

Stage 2: Discover design pattens, and fancy obscure programming constructs.
Use them _everywhere_ regardless of whether it makes sense or makes the code
easier to understand and maintain.

Stage 3: Realize the folly of stage 2, and enter a zen like state where one
writes deceptively simple, clean code. Rarely, if ever, use any fancy
constructs or design patterns (except where it actually makes sense to use
them.)

For the novice programmer looking at someone else's code it's very very easy
to confuse stages 1 and 3.

~~~
jrp
Being a novice programmer, what should I make of this?

~~~
troutwine
You are going to make a lot of mistakes, some painful, others interesting.
Happy hacking!

------
DougBTX
I guess this is what he meant to write, programming language permitting:

    
    
        int Total()
        {
            return dice.Sum(die => die.FaceValue);
        }

~~~
jerf
It should be pointed out, because I fear people are missing this point, that
while the instantiation of his point is language-specific, the general point
holds regardless. In Haskell, to take the opposite extreme, "foldl (+) (map
dieSum dies) 0" would be idiomatic (or would be if the sum function wasn't
built in to Prelude as well but I feel that's cheating too much), whereas the
closest equivalent to the loop approach in C++ would be grotesque and
unidiomatic. How grotesque would be up to the user; you could go all the way
to having the die values in an array and iterating an index through them if
you like, but it'll make the C++ version of that look pretty sane.

The exact algorithms aren't the point. It's really something more like "stay
idiomatic in the language you are in and keep it simple". Another example is
when people start going nuts with lambda in Python; you're really not supposed
to do that and there is almost always a better way that doesn't involve
lambdas.

~~~
DougBTX
Yes, staying idiomatic is a good idea, it's worth pointing out explicitly.

As a tangent, while the original author was trying to abstract away the loop,
I feel he left too many fragments of the loop behind, such as the start and
end conditions. As such, I don't think the sum function is cheating too much,
and is in fact the goal. I might describe the Total as "the sum of the dies'
face values," instead of the more verbose: "the sum of the dies' face values,
starting from the first die and ending at the last die."

In a sense, this runs parallel to your reminder to stay idiomatic: when you
sum a list of things, it is common to add them all together, not just some
subset. It is this shared understanding which lets us be succinct.

Though I do fear that I'm preaching to the choir :-)

------
jrockway
I disagree. I like the second example, because it explains what it's trying to
do -- accumulate, over +, the values of the dice from begin to end. Instead of
dumbing down the algorithm for the machine, it uses the words in the problem
domain to describe the problem. (Now, you could argue that C++ is messy
because you are using a hammer to screw in a screw, and that's true. You could
also argue that it's stupid to rewrite code in this new way when the old way
works fine and the problem is so simple. Also a valid point.)

Oh, and I don't know Boost, but I guessed that the error was a missing _
before the 1. And after reading the linked article, it turned out that I was
right. So it's not _that_ hard to figure out.

I think the article makes sense if you consider its source. This is Zed Shaw,
who has given up high-level languages in favor of C, at least for the purpose
of blog posts. Of course he would prefer the "how the machine does it" version
of the code to "how the programmer thinks of it" version that Boost (and
presumably Ruby) prefer.

I think 50 years of programming has proven that approach wrong, but he is
entitled to believe whatever he wants to believe. I take the opposite stance
-- describe your problem as precisely as possible, and let an optimized
library or language figure out how to get the computer to solve the problem
quickly. You can do both, of course, but keep the two parts separated!

------
p3ll0n
Great Post!

While a lot of factors go into determining whether a language is readable I
have always felt the most obvious is familiarity. The human mind is very good
at adaptation, and often it’s astonishing what we will perceive as "normal."
Familiarity only comes from constant exposure, though, which means that
languages with relatively simple syntax become familiar more quickly. Lisp is
at one extreme, with only one syntactic construct. It’s very easy to become
familiar with Lisp, although grasping the large Common Lisp standard library
is another matter. I tend to agree with the author that C++ is a language at
the other extreme. Most C++ coders I have encountered use only a relatively
small subset of the C++ language. Worse yet, everyone uses a slightly
different subset.

Of course, the biggest impact on readability comes not from the language, but
from the developer. A poor developer can write illegible code in any language.
A good developer? I’ve even seen well-written, readable Visual Basic code
(once).

------
albertzeyer
Such functional code in C++ doesn't need to be bad. Though I would have coded
it a bit different:

    
    
        int Dice::total() const {
            return sum(dice.begin(), dice.end(), _1 ->* &Dice::faceValue());
        }
    

with:

    
    
        template<typename T, typename Iter>
        T sum(Iter begin, Iter end, function<T (Iter)> f = *_1) {
            if(begin != end)
                return sum(next(begin), end, f) + f(begin));
            return 0;
       }
    

This allows some more flexibility. For example, you could also provide other
implementations of sum. You could also easily make your code threaded without
changing the code of Dice::total (well, take this example a bit carefully --
of course it implies that faceValue() is threadsafe and also does only make
sense if faceValue is expensive to call).

Summing up some numbers may be anyway not the best example for this because
the naive implementation is really so short and trivial and in most cases
enough.

~~~
ramchip
Tail call optimization is not guaranteed for optimized C/C++ programs, and is
not done at all in debug mode, so this kind of implementation can cause
trouble with large structures.

I used to have fun, too, implementing tail-recursive functions to do stuff
like flashing a LED on microcontrollers. I have since recovered :)

It is also my experience that helper functions like "sum" tend to be used very
few times, and need a lot of variations, so it's actually better to just write
the loop when needed. The theoritically advantageous functional programming is
hard to convey into C++. Syntax and other technical reasons are partly to
blame, but the biggest has to be simply the culture. Very few C++ coders think
functionally, and coding, especially in C++, tends to be a team job...

~~~
albertzeyer
The tail recursion was not my point. I also could have given another
implementation of sum.

I wanted to demonstrate that the implementation of Dice::total() can be much
clearer and still in a similar functional way.

------
iamwil
I dunno. This reminds me of that post about "Say what you mean in javascript"

<http://news.ycombinator.com/item?id=1358753>

Sometimes, things are confusing or are seemingly super weird depending on your
background. While I haven't done C++ in years, I recognized that
std::accumulate() was probably very much like Ruby's inject().

I suspect those that have seen map, inject, and their ilk wouldn't be so
confused.

While you can argue that the 'average corporate programmer' wouldn't know what
the hell it is, and you might be right. But if we stuck with that attitude,
we'd still be using goto's liberally because the 'average corporate
programmer' would find for loops weird and confusing.

~~~
loup-vaillant
> I suspect those that have seen map, inject, and their ilk wouldn't be so
> confused.

No doubt. However, C++ stays a major contributor to that confusion. In ML
languages,

    
    
        let total = foldl (+) 1 (map face_value dice)
    

is nearly idiomatic. Compare that to the C++ code. Compare that to C++ code
that would use accumulate _and_ an equivalent of map.

~~~
lsb
And in Haskell,

    
    
      foldl (+) 1 (map face_value dice)
    

would use fusion to avoid causing map to create another array!

~~~
ramchip
Why does the sum start with 1? Why not eg.

    
    
         foldl1' (+) $ map face_value dice

~~~
loup-vaillant
It should have started with 0. Sorry. And I avoided `foldl1` because I wanted
to stay close to C++'s `accumulate`.

------
sosorry
I don't particularly understand what makes the second one more confusing than
the first.

In particular, as a non-c programmer this: "(*i)->faceValue()" would be pretty
confusing to me.

The first one in 'English':

"For each i starting at dice.begin(), while i is not equal to dice.end,
incrementing i. Add to 'total' the face value of the thing that i is pointing
to."

The second one in 'English': "Accumulate from dice.begin to dice.end,
initializing the accumulator to 0, adding the face value of each die."

The first one might be idiomatic C++, but it doesn't seem to me that it would
be idiomatic in any natural language.

(reduce #'+ (map 'list #'faceValue dice))

what is obvious depends on your background.

~~~
Avshalom
Really?

bind(std::plus(), _1, bind(&Die::faceValue, _2))

reads as "adding the face value of each die." to you? how. Because not knowing
bind() or c++ I'm wondering what _1 and _2 means. More importantly why bind
seems to take an operator, _#, and something, on the outside but takes only an
integer and _# on the inside call. and, although the surround code might
explain it, where 'Die' came from.

Unless you're just overlooking everything on that line except Die::facevalue.
If you are, then why keep mentioning the 'i' in the for loop version.

~~~
sosorry
Yes, really!

I do a lot more functional programming than I do imperative programming. An
anonymous function in context of an accumulator is obvious to me.

The point that I maybe didn't make so well was that the second version is
working in higher level constructs. The first version is doing a lot of
integer and pointer manipulation, the second is summing across a collection.

The two versions might be speaking in different languages, to some extent, but
that doesn't mean that one or the other is aesthetically wrong.

------
Bjoern
Wish zed would have a date on the blogpost of when he posted that.

~~~
FraaJad
``import time; print time.ctime(1276445247)`` where 1276445247 is the post's
filename.

~~~
Bjoern
Ah, I see, thanks for that.

------
pbiggar
This would have been fine with a BOOST_FOREACH. I find it hard to believe that
he knows his way around boost:bind, but not BOOST_FOREACH.

~~~
SamReidHughes
Take one look at the code and dependencies behind BOOST_FOREACH, and you'll
thunk twice before using it.

~~~
pbiggar
I have taken a look (and read the article about it). I'm not sure I agree that
this should stop you using it. Its like saying you shouldn't use Rails because
there is deep magic going on.

------
benatkin
Did anyone else notice the inconsistency without Zed's help?

Even if I hadn't, it still wouldn't have affected how I thought about the
code. It's just a type error that would have been caught by the compiler.

[http://www.boost.org/doc/libs/1_43_0/boost/bind/placeholders...](http://www.boost.org/doc/libs/1_43_0/boost/bind/placeholders.hpp)

------
shin_lao
So in other words the author isn't used to boost::bind and stl algorithms and
complains that he doesn't understand code written with both?

First of all today you would rather use a lambda which is easier to
understand.

The advantage of the second code is that it's generic and less error prone.
You can't get the loop wrong. You can change the addition by any operation
very easily, and the compiler will complain when you get things wrong.

fyi, the same code written with a lambda:

    
    
        int Dice::total() const {
          return std::accumulate(
              dice.begin(),
              dice.end(),
              0,
              [&](size_t v, const Die & d) -> size_t { return v + d.faceValue(); });
          );
        }
    

For more information about why the second version is better, I advise the book
"Elements of programming".

ps: the spelling error introduced by the author prevents the code from
compiling, so the argument doesn't hold

~~~
beagle3
errr, no.

It is in no way more generic or less error prone. Just asserting that does not
make it so.

You _can_ get the loop wrong, if you accumulate(dice1.begin(), dice2.end(),
...) for example, or switching the begin() and the end(). You can just as
easily change the addition operation to anything else in the non-boost/stl
version.

In the 2nd version, when you _do_ make a mistake, if the compiler catches at
you get an error message that's totally unreadable with 2000 character error
lines. And if the compiler doesn't catch it, and it causes a runtime problem,
it is also extremely awkward to backtrace.

Not to mention, you need the latest-and-greatest compiler to do the lambda
thingy, for no obvious benefit.

~~~
shin_lao
Most STL implementations catch the two errors you gave in debug mode
(switching begin and end, using two different containers, etc.), and many
more!

As for the compiler giving crazy errors, it's true for g++ mainly. VC10 and
most of all clang give intelligible messages.

~~~
beagle3
I haven't been following STL implementations recently, but that definitely
wasn't the situation even in 2007 (Over 10 years after STL was finalized..);
In fact, the only implementation that it did back then was STLPort, at a huge
performance hit, and only at runtime.

And what you say about compilers is EXACTLY why I wrote the "latest and
greatest". It's only recent clangs that can give intelligible messages for C++
(if at all) because Clang C++ support is not even ready for production yet.

VC++ 2005 (last I used) did not get give reasonable errors.

------
edanm
I suspect that some of the programmers who find the second version better are
non-experienced. To them, the first version is _not_ standard C++ code, so it
looks as "ugly" as the second version. The second version is cooler (= harder
to understand), so they prefer it.

Having said that, the second version written in a better language _is_ pretty
standard. It's mostly the limits of C++ which make the "functional" version
look so bad, imo.

