(1..100).inject(&:+)
I want to show you my favourite line of ruby code:
1, 2
(1..100).inject(&:+)
This code works out of the box in Ruby 1.9. This also works in Ruby 1.8 with Rails, thanks to this chunk of code called “Symbol#to_proc”:
class Symbol
# Turns the symbol into a simple proc, which is especially useful for enumerations.
def to_proc
Proc.new { |*args| args.shift.__send__(self, *args) }
end
end
Now, I should explain what I like about it. No, we aren’t going to debate whether
{ |acc, n| acc + n }
contains accidental complexity. We are not going to talk about why this is better than whatever you would do in PHP or Visual Basic to sum the integers. Let go of any idea that this article is about golf. I like this line of code because it says a lot about how the Ruby language works. I do not want to talk about those other things, they are not interesting to either of us.
The two things I
do want to talk about are that: 1. Somebody made Symbol#to_proc in Ruby 1.8, and that 2. It became part of Ruby 1.9. Let’s start with how it works.
Symbol#to_proc works because Symbol is an
Open Class in Ruby. And Ruby is
Dynamically Typed, meaning you can modify classes and objects at run time, thus Symbol gets the to_proc method added when you run the above code. (You sometimes see the expression “Dynamic Typing” used to describe a system where you don’t need to write the type of an object before using it. I call that “Latent Typing” and use the word “Dynamic” to describe a system where types can
change thanks to adding or removing methods. In this case, we are adding a method to
every Symbol in a program, which is pretty dynamic.)
The Acid TestOne of the things that really intrigues me this little snippet of code,
(1..100).inject(&:+)
, is that you can tell an awful lot about someone’s attitude towards programming styles, languages, and cultural bias by asking them what they think of it. It touches on functional programming, on brevity, and in this case, on open classes.
Take a moment to really think this through.
What do you think of the fact that someone sat down and modified one of Ruby’s most core classes, Symbol?This is a decidedly non-trivial thing to do. Changing Symbol in any way opens up a Pandora’s box of risk. You can break a lot of code. You can—I beg your pardon, you
will—confuse people who have never heard of Symbol#to_proc. Just so you can express “Fold the numbers from one to one hundred using addition” in a very direct manner eliminating the boilerplate of
{ |a, n| a + n }
.
If Symbol wasn’t an open class, how would
inject(&:+)
have been added to the language? Open classes and other “turtles all the way down” features are
dangerous. But dangerous features help a language evolve. Lisp’s macros are extremely dangerous. And they are also what made it possible to discover/invent CLOS. Had there been no macros, the only way to experiment with new ideas in OO would be to create whole new language, an exercise in Accidental Complexity.
The Ruby Way is the perfect second Ruby book for serious programmers. The Ruby Way contains more than four hundred examples explaining how to do everything from distribute Ruby with Rinda to functional programming techniques.
When you look at a language like Java, you see this ponderous, bureaucratic process for language change. Java does not include dangerous features like macros or open classes, and as a result change can only come from the language’s implementation, not from out on the
fringe where people are using it and creating their own features to address their own needs.
(As someone pointed out, not all languages with centralized control evolve as slowly as Java. But they still are centralized, they still impose a vision from the top. The point is about central planning vs. the free market if you to toss another metaphor on the pile.)Lispers talk about Bottom-Up Programming. Well, dangerous features enable bottom-up language evolution. We discovered we like Symbol#to_proc because it bubbled up from the bottom. Someone invents something. If other people like it, they use it. The word gets around. People improve on it. Eventually it gains acceptance and becomes the de facto way to write code.
This is true in all languages, but languages—like Ruby—that include dangerous features give the fringe a broader latitude to invent new things. Of course, they also break things and they invent stupid things and they get excited and write entire applications by patching core classes instead of writing new classes and commit all sorts of sin.
The number of readers who seem unable to get past the literal syntax-as-solution of (1..100).inject(&:+)
and grasp that the interesting bit isn’t the literal utility of that line of code, or that there are others ways to implement the same functionality, or that pet language X has an easier/smaller/cleaner mechanism for summing integers, or that there are “better” lines of “cooler” code, is really quite depressing to me.
Is it really so difficult to work out that the interesting bits are in the mere existence—or not—of that code at all, per the developer’s whim?
—Chris Cummer
But they will also invent Symbol#to_proc. The “marketplace” has voted very strongly in favour of Symbol#to_proc, so much so that it is now in the next revision of the language. Maybe you love Symbol#to_proc, maybe you hate it. But somebody loves it, enough to have created it, and enough other people liked it that you can find implementations everywhere. So it clearly has some appeal. But that isn’t the point. Even if it is a terrible feature, it is a feature invented by the people and for the people, not a feature pushed down from above.
Open classes makes invention possible in real time. No committee. No waiting for the next language revision. If you think of it, you can try it. Does Symbol#to_proc make it worthwhile? How about
andand? Why don’t
you tell
me? I was serious when I asked you what you think of it.
Are you young at heart?For some, languages are best when they are mature, when there is nothing left to discover, when all of the “best practices” have been laid down in stone. There’s a comforting stability, a knowledge that some hot shot they hire tomorrow will not arrive with a bunch of new techniques you have never heard of. In mature, stable environments the people around the longest have the most authority.
When anybody can invent a new thing, it’s the youngsters who hold the power. If you don’t like the way Ruby handles nil, you can write your own
andand. Surprise surprise, so did half a dozen other people, so for the moment you’re going to find that there is no clear standard, no “One obvious way to do it” in Ruby. That kind of thing is
painful for a certain type of personality: they want to know “Which way is the best.” Ideally, they want Matz (or as one person commented, Guido) to anoint one of these the “winner” by building it into the core. That way the oldsters get the power back, they have the experience so they have memorized more of the core API.
Invention isn’t its own reward. Creating new stuff for the sake of novelty wears thin pretty quickly. But when you are making up the rules, you get to stack them in your favour. If you create it, it works for your needs. Naturally, there is an argument that the mature, tested heavyweight whatsis is better than your half-baked roll-your-own whatsis. or at least, there is an argument if we are talking about an Enterprise Transactional Data Processing Platform.
But look at Rails. It is much smaller than something like Spring. So it was much better for 37 Signals than Spring, even though they rolled their own. Rolling your own is a lot harder in a language—like Java—that doesn’t let you have the dangerous features. The trade-offs between using the old and inventing the new are different in a language like Ruby. You still want to use what already works most of the time. But there’re a lot more places where inventing something new makes sense.
Ignorance is StrengthNaturally, a lot of people are going to get the trade-off wrong. They’ll reinvent something that already exists. Or they’ll gratuitously use a dangerous feature when a perfectly serviceable safe feature is easier and faster to use. That’s the consequence of giving people a choice, sometimes they choose wrong. Especially if they are young, especially if they don’t know any better.
But guess what? Not knowing any better is what made David create Rails. he didn’t know Ruby doesn’t work for web applications. He didn’t know it’s wrong to create a web framework when there are so many sitting on the shelf waiting for you to use them right out of the box.
When I look at some code like
(1..100).inject(&:+)
, I see the thing in Ruby that made Rails possible. I see all of the good things—and all of the bad things too. In that way, it’s totemic. And in truth, I think you can really decide for yourself what you think of Ruby just from looking at that one snippet of code.
If it scares the bejeezus out of you, if you start thinking of rules to impose on your team (we will use no more than three of the following seven gems on any one project. You will have an Architect approve any modification to one of the following 132 classes…), then Ruby is not for you.
But if you love
(1..100).inject(&:+)
, if it fires up your imagination, if you think that you might write your own gem one day and see if the community likes your idea for improving the language, then I think you will be able to handle the inevitable snafus with a pragmatic shrug of the shoulders (
Maybe I shouldn’t have extensively patched the Array class just so that I could steal the { x => y } notation for my pattern-matching DSL). You will evolve your judgement so that you know when to pull out the dangerous features and when to use restraint.
And whether you love open classes or hate them, whether you can think of a way to use them or whether you think there is always a better way, the indisputable truth about them is that Rubyists
are using them to evolve the language from the bottom up, to find new ways to do things. Good things, bad things, beautiful things, ugly things… they are all New Things.
And ultimately, that is what this line of code says to me about Ruby. It says that this is a language where the fringe is inventing new things. And to embrace ruby is to embrace the idea of a language being propelled by its user base.
I use the language, so I obviously have some degree of comfort with the idea of a language evolving from the bottom up. But what do
you think? It’s your opinion that really matters, not mine.
- Explanation:
(1..100)
creates a Range. For our purposes, this is equivalent to a collection of the whole numbers from one to one hundred, inclusive (The major way in which it differs from an array for us is that it doesn’t actually require one hundred spots in memory. Ranges are also useful for matching.) The #inject method is called fold
or reduce
in other languages. Expressed like this, it applies the “+” function to the elements of our range, giving us the sum. Primary school students could tell you an easier way to obtain the sum, of course, but we will ignore that for now.
Ruby doesn’t actually have a function called +
. What actually happens is that #inject
wants a block. The &
operator converts Proc objects into blocks and block into Proc objects. In this case, it tries to convert the symbol :+
into a block. The conversion uses Ruby’s built-in coercion mechanism. That mechanism checks to see whether we have a Proc object. If not, it sends the #to_proc
method to the argument to make a Proc. If the Symbol :+
has a #to_proc
method, it will be called. In Ruby 1.9 it has a #to_proc
method. That method returns a Proc that takes its first argument and sends the +
method to it along with any other arguments that may be present.
So, &:+
really means { |x, y| x + y }
. And the whole thing gives us a simple sum, just as (1..100).inject(&:*)
would give us the product. Now that you know that, what does (1..100).map(&:to_s).map(&:size)
do?
[back] - My favourite? Really?
Actually, yes. Not the most powerful. Not the most compelling. Not even the most concise, and I don’t defend it as being superior to (1..100).inject { |acc, n| acc + n }
in any way. And not my favourite line of code from any language: Ruby isn’t my favourite programming language, so it would be surprising if this were my all-time favourite line of code.
But it is—to me—a totemic line of Ruby code. A line that demonstrates what a lot of Ruby is all about as a language. Its good and its bad. People have written to say that you coul duse sum
or whatsis or jeebus to do the same thing in Ruby or in other languages, so this is a bad example. No it’s not! Trying to write an unassailable line of code that everyone will worship for its perfection is a fool’s errand. The point is to demonstrate something of Ruby’s character, especially in its evolution.
As someone wisely pointed out, the equivalent code in other languages needn’t sum the numbers from one to one hundred, it needs to demonstrate something about that language’s culture and evolution. Java might show something with generics and type erasure. C# might do something with LINQ.
[back]