Zed is making a number of points, so I don't want to sound like I'm arguing against all of them. But particularly on the matter of each... if all we were talking about were each I think Zed would have an excellent point. But wrapped up with each are all of the other fantastic enumerable methods like map/collect, select/find, inject/reduce, as well as each_cons, each.with_index, map.with_index, etc. These are where the power and expressiveness of ruby really come into play. For example:
With the for each construct you'd have to pass in an accumulator variable and keep track of whether to sum or not. While you certainly can do it in a for each construct, I'd argue that you'd be missing out on what makes ruby, ruby. And getting beginners in that more functional(ish) frame of mind sooner rather than later is a very good thing.
You are right, and once again I am not against using functional programming, OOP, message passing, or any of the things I mention about .each.
I'm against using .each as a first looping construct. In fact, go look at your supposedly better last line of code and tell me if you think a newbie would even begin to comprehend that? Hell I'm a programming veteran and Ruby veteran and I want to metaphorically punch you in the gonads for that line of code.
To clarify, I don't think my original post implied that you were against teaching people how to learn each. Nor do I want to start out teaching beginning rubyists everything all at once. It was more to point out how important the mindset of each is. That's the mindset that you want to reach. And I'm not sure that teaching a construct that you'll never end up using is helpful. I think it just creates a mental construct that you'll have to overcome at a later stage.
I don't think .each is so much harder to learn that it justifies getting a beginner in the wrong mindset. Though to be fair I've only ever explained it to people who've already known some programming, so there is a possibility that I'm underestimating the difficulty of teaching it.
In any event, I thought the essay was totally worth the read even if I didn't agree with all of it. Thank you for it.
I'm an experienced Rubyist. Really. I started using it in 2002.
Depending on the context, I will use a for-loop in my Ruby. Not often, preferring the power that #each provides and backs (e.g., all of Enumerable), but there are times when a for loop is absolutely the clearest way to write what I'm doing.
The only place that I'd change what Zed said with respect to this particular example is that, in Ruby, there are some side-effects to using a for-loop instead of #each. Most of the time, they don't matter, but they do exist. (So in using the for-loop to teach programming, I'd have a footnote that says that the preferred way in Ruby will be discussed later.)
Interesting. Any examples of where you find for more clear than #each? I feel #each is inheritly more clear because it's explicit. Every time I see a for-loop in Ruby I have to remind myself that "oh, that's actually just calling #each". It's also easier going from e.g. #each to #each_with_index, or #each_cons/#each_slice.
Mostly? DSLs and templates. The goal of an in-language DSL isn't necessarily to feel like you're writing that language (I wrote a DSL once that made it easy to generated shared enums for C++, C#, Java, and Ruby. It was Ruby behind the scenes, but because it was declarative, it didn't feel like Ruby. Additionally, the templates used to generate the resulting code almost exclusively used for loops.)
I almost exclusively use the #each-ish in my libraries and in the backing code for such DSLs or templates.
Now back to the blog post and see how many constructs to understand .each correctly vs "just accept it for now that this is how it should work, we'll come back later".
Have you ever read such books? the ones with "we'll come back to it later".
>I'm not sure that teaching a construct that you'll never end up using is helpful.
But that was precisely Zed's point! You will certainly end up using it, though you may never use it in Ruby. .each is not transferable, and for-loops are. Zed's teaching Ruby as an introduction to programming, not just to Ruby in itself.
.each as a concept of "iteration" is transferrable, no?
Also, I think there are some serious consequences when programming is taught in such a "diluted" way.
As a programmer who is constantly learning new concepts/languages, I am continually understanding the very real trade-offs when I write something in Ruby vs. C, not just in functionality, but in the design of the language itself.
By diluting, all languages start to blend in this ball where they become indistinguishable. Besides, if Zed is teaching an introduction to programming, it becomes completely arbitrary what language he chooses, and it therefore means he doesn't need to do variations of his lessons in other languages - unless of course he was going to meaningfully divulge into how these languages propose new ways of approaching a problem, etc. (but this would render this article's arguments moot, to a degree)
The examples you give are equivalents to Enumerable#map, not Enumerable#each. In JavaScript, the closest equivalent to #each is Array.forEach(). In C# and Python, while methods equivalent to #each may exist (I'd have to double-check the docs to be sure), the recommended way to accomplish the same functionality as #each is...a for loop.
> More significantly, it has a Parallel.ForEach which is recommended for iterations that can be done in parallel.
Yes and no. I've seen Parallel.ForEach misused more often than I've seen it used right. If you have a small ( < 16) number of loop iterations then don't bother with Parallel.ForEach. If each of those items to process is time-consuming then use tasks and Task.WaitAll for them to complete. Where Parallel.ForEach works well is where there are a very large number of iterations, and the scheduler can split batches of them up between cpu cores, and even tune the batch size as the run progresses. That's just not going to happen when there are 5 loop iterations.
#each and #map are not meant to be the same thing. #each in any language is meant only for side-effects. If you only care about the return value, #map is a much better choice. (Similarly, if you only care about the return value but want many items condensed into one "reduced" item, you want #reduce.)
I'm guessing Zed first learned to program using for loops so they look "natural" and easier for beginners to learn than functional callbacks. That seems like a testable proposition, but I'm guessing it isn't so. There aren't too many situations in life outside programming and racing where people go around in circles to get something done. Furthermore, the choice of print in the example begs the question. Are you teaching that imperative programming is natural and functional programming is only for "advanced" students? What are you going to teach the beginner when the goal is calculate the sum of the squares of a list of numbers?
FWIW I learned on BASIC. "GOTO" looks natural to me. It's also much closer to the machine. Structured programming is a prettification of test-and-jump, and you have to be able to see through it, to use it well.
Functional programming, on the other hand, is a bit of a conceptual leap away from the metal. If you're mapping two functions over a stream of data how many loops is it? In a dumb language like Ruby, it will be as many as your map() calls. But in Haskell, it will fuse them into one loop doing two operations. And the thing with functional programming is, you're not supposed to care - except, you do have to. Generating an endless unresolved series of thunks in Haskell is a beginner mistake. But if you can't see through the functional list-flipping to the generation and consumption of callable heap objects in a test-and-jump cycle, you're going to be scratching your head.
My answer would be to teach both assembler and FP, and step up and down simultaneously. Start on Excel and move to Haskell, while also starting on 6502 or some such toy assembler, and moving to C, and meet in the middle at Scheme.
In a very real sense, imperative programming _is_ natural. Its much closer to how computers work. Whether this means that its an easier way to learn is up for debate. (For the record, I think it is easier for a majority of people, but that functional would be easier for a significant minority.) Your phrasing of that part just kind of bothered me.
I think we already teach beginners that you calculate the sum of the squares of a list of numbers with a for loop. Actually, its a fairly common type of problem when you're learning about for loops. Maybe I'm missing your point?
Natural != how the machine works. Few of us have the faintest clue about how the laws of thermodynamics apply to the internal combustion engine or how solid state physics leads to semiconductors leads to electronics leads to electronic injection. Yet we all drive cars as naturally as we walk.
I'd argue that your analogy strengthens my point. In their original form, cars involved people manipulating the mechanical parts as directly as possible (the steering wheel was completely tied to the front axle, the gas pedal, brake, starting crank all directly controlled a single aspect). And yet, we drive cars so naturally. I think using the word natural in this sense makes a lot of sense.
Ruby gets `each` specifically and blocks in general from Smalltalk, so I'd be willing to bet there is research showing whether it is easy or hard to pick up. I'd further wager that the answer is that it is no harder. Loops are more "natural" for the machine, and therefore anybody who understands the machine; that doesn't mean they're more natural for the student.
This is a good point. To play a little off your blog post, a person new to Ruby could view the second line of code more as an idiomatic expression (which it's not really, but it can have the appearance of being one) in that it doesn't make obvious logical sense and is harder to translate to a more familiar language...giving a "raising ravens" (Spanish) vs. "can of worms" (English) scenario.
I think it'd be kinda cool if one could develop sort of a "ruby-normal-form" that could take some ruby expressions and translate them into a more normalized form using things like for-loops, etc....granted normalizing a lot of ruby code might be hard, intractable, or maybe even impossible.
If it threw in explicit returns at the end of each method and added back in all the optional parentheses, you'd be close to the average "Ruby code written by people who don't know Ruby that well" codebases you see in the wild so often.
I kinda liked it actually. It's a linear sequence of simple operations, and there is an explicit connection between each successive operation (i.e. subsequent operatons are performed on the return value of the last operation).
That's what I personally love about method chaining anyhow.. Functional composition with a structure that's not completely ad hoc is kinda nice.
If you hate that last line, you must hate Unix pipelines as well. Because both accomplish similar things and are surprising to newcomers in a similar way.
When I was teaching myself how to program in VBA, I remember thinking "there's got to be some syntax to take a list of things and iterate over each of them." I was of course looking for a for loop (or for each). Without knowing to even google "for loop in VBA", it was a pretty big deal to finally discover, "Hey, programming languages have loops!"
Later, after coding for a couple of years and picking up .NET, I eventually learned LINQ syntax and really started to appreciate the expressiveness of chaining methods together to iterate over collections. It was a nice addition to my tool box, but it still took some practice more me to grok it. If that had been my first exposure to loops, it likely would have been a stumbling block to my learning.
Chapter 12 was 'looping'. I'm not claiming I was some programming genius, but to have been given a computer with a manual that, in < 100 pages spelled out the basic constructs of programming logic... that was magical. And useful. And empowering.
I know we've all got the google at our fingertips today, but I suspect that many people don't even know how to express what they want to do in basic enough terms to search - thedailywtf has examples of that (sadly). I don't know if there's a way to reverse this, but it struck me that we've definitely lost something (or perhaps 'we' never all had it) when you state: 'Without knowing to even google "for loop in VBA"'.
--edited a few times for formatting/clarity