Programming Languages Have Social Mores Not Idioms
learncodethehardway.org
learncodethehardway.org
Now "idiomatic" also has two senses. First, it can mean "having to do with idioms". Second, it can mean "peculiar to a particular group, individual or style". (See http://www.merriam-webster.com/dictionary/idiomatic)
So, I think when people speak of idioms regarding programming languages they're using the second of the above senses. While this sense might be characterized as having to do with social mores, I don't think it's a misuse of the word "idiom". People don't mean to say that .each statements are non-compositional in the way that "kick the bucket" is. Indeed, given that programming languages have formal grammars/semantics it's hard to imagine what non-compositionality would be at all (though I'm sure any good Perl programmer could probably give some examples!)
1 : of, relating to, or conforming to idiom
2 : peculiar to a particular group, individual, or style
That again says it's a local construct that isn't a clear universal usage. If you want to be clear in your writing then you avoid idiomatic speech.
This also doesn't disprove my point that the way these supposed "idioms" cause derision and overreaction says they are now social mores.
Idioms, by definition, aren't universal. (By etymology, too: an idiom is idio-, one's own; think idiosyncracy or idiolect.) it. It's very odd to contrast the idiomatic and social mores.
The linguistic analogy is really confused, anyway:
"These idioms are also not language dependent, but actually locality and culturally dependent. I could move to England, the home of English, and they'd use many new and different idioms.
What you don't see is people in England demanding that I say "the dog's bollocks" in order to visit."
1. There is no single language "English" which has its home in England but is also spoken in America, Australia, Canada, South Africa, etc. The contrast between language and locality/culture is extremely tendentious.
2. You had better believe that certain changes of locality and culture are accompanied by demands that one change one's idioms. If I go for a job interview at a conservative law firm, I am not going to praise something by calling it "the shit". If I normally spoke AAVE, I would do my best to suppress that.
3. American English and British English are mutually intelligible (but there are chains of mutually intelligible Englishes whose extremities are not mutually intelligible), so you can speak AmEng in Britain. If you want to speak the way your hosts speak, though, you'll say "boot", "lift", etc. when you would otherwise have said "trunk", "elevator", etc., and you might find that some of your American idioms actually aren't comprehensible. Your British hosts might well demand that you say something they understand to them.
4. You can speak AmEng in Britain, and you can write Fortran in Ruby. It is pretentious for an American to affect an accent while in, or having come from, Britain. By contrast, it is only reasonable to do things Rubyishly in Ruby.
"Because people quite literally freak out when they see non-idiomatic code, and go to great lengths to correct everyone who doesn't write idiomatically, these can't be idioms."
So ... what do you think gets taught in elementary, middle, and high school english classes? Idiomatic formal english! You are corrected if you don't write idiomatically! Many people freak out when they see nonstandard forms of English!
1. a. Peculiar to or characteristic of a given language. b. Characterized by proficient use of idiomatic expressions: a foreigner who speaks idiomatic English.
That use of the word 'idiomatic' is very common, and although it's related to the word idiom, it's not quite the same thing. An idiom, as you say, is a phrase that you can't guess the meaning of just by glossing each of the individual words (a favorite example - in Puerto Rico, though not all Spanish-speaking countries, a flatterer is a 'lambeojo', literally 'eye-licker'). But someone who speaks Spanish idiomatically or who writes idiomatic French does so 'like a native'. It's a good thing. So when someone says that 'each' is 'idiomatic Ruby', that's probably what they have in mind.
All of that said, I can understand wanting to teach beginners the for loop first. But I don't think a Ruby course should finish without looking at each. If a person plans to read any real Ruby, they had better be ready for lots of iteration with each.
users.select(&:meets_some_critera?).each(&:update!)
You could totally do that in a for each loop if you wanted. But it isn't really composable. While this works for the basic update... for user in users do
user.update! if user.meets_criteria?
end
... what if you wanted to then sum up the new account balances? In the first example, you'd just chain on a couple more enumerable methods: users.select(&:meets_some_critera?).each(&:update!)
.map(&:account_balance).inject(&:+)
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.--edited a few times for formatting/clarity
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.
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.
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.
- First loop programming construct
- Beginner
Exhale. Inhale.
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".
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.
Javascript has Array.map(), which is almost the same:
var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);
/* roots is now [1, 2, 3], numbers is still [1, 4, 9] */
C# has IEnumerable.Select(): IEnumerable<int> squares = Enumerable.Range(1, 10).Select(x => x * x);
/* squares is [1, 4, 9, 16, ...] */
Python has map(), which isn't OOP but accomplishes mostly the same: names = ["mary", "john"]
caps = map(lambda name: name.upper(), names)More significantly, it has a Parallel.ForEach which is recommended for iterations that can be done in parallel.
It's trivial to add it as an extension method though. I've seen it done, and I wanted to punch them for using needlessly overcomplex idioms. It's discussed here http://stackoverflow.com/questions/225937/foreach-vs-somelis...
> 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.
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)
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.)
I almost exclusively use the #each-ish in my libraries and in the backing code for such DSLs or templates.
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.
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?
That's what I personally love about method chaining anyhow.. Functional composition with a structure that's not completely ad hoc is kinda nice.
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.
30 years ago, I got a computer, and it came with this: http://www.worldofspectrum.org/ZX81BasicProgramming/
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"'.
:/
I think most programmers understand what is meant when people say a programming "idiom", just like most people understand that it's OK to call small representative computer graphics "icons" even though they're not physical religious symbols.
Words gain new meanings in new contexts, that's how language evolves. Complaining about it after-the-fact is just stupid. Nobody's going to start calling these "social mores" because of this article.
And as to why ".each" is a horrible looping construct, the only justification is that it's harder to teach. Well, sure, if you're teaching someone programming for the first time it's probably better to start with "for". That doesn't mean ".each" isn't superior for experienced programmers, though. The author doesn't address any of the advantages to ".each".
- we should be calling "idioms" "social mores"
- we should teach beginners simpler constructs instead of idiomatic ones, especially `for` instead of `.each` in ruby.
Note that you agreed with his second point.
He has no responsibility to address the upsides of `.each`, since that's not the point of his article. He wasn't talking about advanced programmers, he was specifically addressing why he didn't teach `.each` in his book, and why it shouldn't be taught to other beginner programmers.
So I don’t think “each” is any better than “for”—in addition to having uglier syntax in Ruby, neither conveys any more information than the other. It’s the same with std::for_each in C++, present only for consistency with its more meaningful siblings.
> Well, sure, if you're teaching someone programming for the first time it's probably better to start with "for".
That said, I was inclined to agree that idiom has simply picked up a new meaning. That's usually where I fall in debates about changing language. However, your straw man argument almost made me change my mind out of spite.
I'd argue that learning .each vs. normal looping has numerous benefits. First, this is the way most people loop most things in Ruby. More importantly, however, teaching .each enabled me to be more openminded towards learning "yield" and blocks.
In a way, without having learned about .each first, and coming into Ruby with my preconceptions (or no conceptions), I would have been confused. I would have been writing Ruby as if I were writing Python or C, without really understanding the benefits of writing Ruby as Ruby ought to be written.
Starting to teach languages avoiding what makes the language so special is counter productive. As soon as your students know a basic way to program, they won't be inclined to learn the "good way".
Earlier today I read through Rich Hickey's posts explaining reducers and actually used them to get crazy performance gains from some Clojure code I was writing...but I highly doubt I would've gotten there without Ruby's "each".
I've seen Zed argue on this point before, and I've always been dubious of his "concern". The one point he makes here, though, that makes me side with him is that "for" is easier to teach than "each". Yes. A hundred times: yes!
The point I will disagree with him on is that "for" and "each" are interchangeable. Semantically, yes. Performance-wise, probably. But stylistically, "each" is a gateway drug to functional programming, and that should not be taken for granted.
(Edit: Where this all breaks down, and where I think Zed is experiencing frustration, is that while some people might have had a good reason for using "each", over time it became a social more. Now people teach and even enforce the use of "each" without being able to explain why. That doesn't mean one should avoid teaching "use each, not for", but it does mean that one should go and find out why.)
Start with "for". Teach "each".
...then go learn Clojure because hot DAMN reducers are going to revolutionize the way we program (but that's another topic for another day).
In other words: programmers don't understand pedagogy. Nothing really new there.
I've very much enjoyed the books though...went back and did "Learn C the Hard Way" even though I've been using C for almost 18 years. Can't wait to read "Learn Clojure the Hard Way"... ;-)
Consider this snippet from natural language:
Paul: "Hey nice jacket, looks great on you"
Linus: "Word"
Linus used an idiom to articulate his sentiment. Why? Because the idiom, if used around someone who understands it, offers little uncertainty about the meaning while also being concise.
Linus could have said, "Why thank you. You really didn't have to say that. I appreciate the compliment but I'd rather not spend a lot of time discussing my jacket".
The idiom was useful because it added clarity, both by reducing the excess information going over the communication channel and also by clarifying the intent.
If you use a for loop in situations where the variables used by the loop are needed outside the loop, then there is no reduction in clarity b/c of the choice of a for loop. However, if you use a for loop when the variables used by the loop aren't needed outside the loop, you pollute the code with unnecessary ambiguity.
Ruby 1.8 had this problem with block variable scope and it was fixed in 1.9. Now in 1.9 you can write code like this:
x = 5
(1..3).each do |x|
x = 99
end
puts x #=> will print 5Nevertheless, this use of idiom does seem consistent with its normal usage. In the context of natural languages, an idiom can mean a phrase of arbitrary origin that has come to have a specific meaning in specific dialects (this is referred to as an idiomatic expression), but the adjective idiomatic can also be used in the sense of "what sounds most conventional to a native speaker" [1]. Likewise, in programming languages, I think of something as being "idiomatic" if it follows either formal conventions [2] or is a commonly used construct.
As for the author's negative opinion of having idioms in a language, I'd imagine most programmers advocate conventions in order to keep code somewhat consistent across programmers. If you didn't have them at all and if every programmer used different constructs to do the same thing, programmers wouldn't be able to use pattern recognition to read others' code and all code written by multiple people would be even harder to decipher than poorly written Perl since there would be infinitely many ways to do it. TWBIMWTDI? :)
[1] I don't want to say what sounds "right" (hence my use of "conventional") since that would necessitate a lengthy discussion about grammatical prescriptivism, but just note that colloquial expressions like "the people who I gave the ball to" can be considered just as idiomatic (under this definition) as formal expressions like "the persons to whom I gave the ball", even if prescriptivists would define only the latter as "correct".
[2] Unlike natural languages whose syntax is naturally occurring, prescriptivism in programming languages makes sense since they're invented by humans to make writers, readers, and/or revisers of code more productive.
arr.each { |elem| puts elem }
Explanation: There is a function "each" that applies to arrays. "Each", for each element in the array, binds "elem" to the element value, then executes the body of the code block. The code applies "each" to array "arr" and executes "puts elem" for each element. Concepts: variable, binding, function, block, array. Done.The prosecution charges:
> Variables
Basic elementary concept, if you don't get this, you should not be coding yet.
> Arrays
Basic elementary concept, if you don't get this, you should not be coding yet.
> Enumerable
I have no idea what enumerable is, but I can read the code just fine.
> Inheritance
I have no idea what inheritance means in this context, but I can read the code just fine.
> Mixins vs. Multiple Inheritance
For lack of better words, wtf is the author talking about?
> Message passing and dispatch.
Ditto wtf.
> Function calls.
Basic elementary concept, if you don't get this, you should not be coding yet.
> Blocks passed to function calls.
Arguably basic elementary concept, in spite of the long tradition of Basic teaching us otherwise. But then Basic taught us GOTO is a good idea, fortunately we've got past that idea eons ago.
> Yielding variables to blocks.
I have no idea what yielding is or how it helps in this context. Still I can understand the code just fine.
> Scope, specifically for blocks.
Scope is a subcategory of variables, if you don't get scoping then you don't get variables, see blurb on "Variables" section.
The fact that you can read a piece of code doesn't mean that you understand it. The whole point of teaching how to program is to actually get the student to learn what is actually going on, not just get them to a point where they can give a half assed explanation that really doesn't teach a thing. Repeating the line of code with extra words to convey meaning most certainly does not make it a valid explanation.
And then you come along with a list of "responses" to the concepts outlined in the article, out of which you don't even actually understand half of, and completely destroy your initial argument because of it. You complain that Zed is a troll because you can actually read the code easily, when the whole topic of his is actually wether you can understand it. It's a completely different game. You basically just proved Zed right. The problem with teaching .each first according to Zed is that since you don't understand even half of what is going on, you just do things blindly because you've grown to predict the behavior you expect from a specific piece of code.
For dessert I'm gonna "troll" a little bit. 1) If I should know variables, arrays, function calls, blocks passed to function calls (which includes an arbitrary comment about BASIC and goto statements), and scope, before I start coding then how the hell am I supposed to learn those very basic concepts? I mean according to you I shouldn't be coding right? 2) If you really don't grasp the concepts you say you don't understand in the list, you probably don't program for a living and/or have not had to actually teach any programming concepts besides the very basics. If this is the case, I'd argue that you still have a long way to go before actually having the experience to compare both "teaching orders" being compared here. 3) You give me the impression of a guy that has only done a bunch of jQuery and perhaps a Rails tutorial or two... AMIRITE?
I'm sorry if this comes off like a lengthy angry diatribe (rest assured it's not my intention), but you tried to sound knowledgeable and ended up putting your own foot in your mouth. My mother once told me to only open my mouth if I know what I'm talking about. That my friend is my advice for you.
Therein lies the troll. Understanding is not figuring out how to derive the code from first principles. If that were true, you shouldn't drive a car because you can't explain the carburator mix ratio on top of your head. Understanding means being able to copy a pattern and adapt it to a new situation. I never wrote a line of Ruby in my life, but I can take the quoted example and tweak it to do something else without ever worrying what mixins are supposed to mean within the Ruby landscape. Proof:
arr = [1, 2, 3]
sum = 0
arr.each { |elem| sum += elem }
puts sum
6
http://codepad.org/x5roEPvSIsn't it nice that I just invented reduce semantics out of something that ostensibly is only a map?
Go ahead and read Zed's article again, only this time do so slowly and with care. Come back and realize two very important things you seem to be missing:
A) This is Zed's opinion about TEACHING a programming language. TEACHING being the keyword. If you want to learn a programming language, you inherently need to understand it. As a small aside, he did write "Learn Python The Hard Way" which was pretty much a success, but don't believe me... he's probably a troll as you say.
B) The discussion is basically that a form of writing a specific block of code is easier to explain than another, although the latter is more widely used and considered the correct. Readability, though important, has little to do with choosing the correct construct to teach as to provide a good balance of the time and effort you need to put into learning.
You want to keep going with the analogies? You shouldn't drive a manual transmission car if you've only driven automatics, the same way you shouldn't deploy code to your clients that you've figured out will give you the results you want when you've never understood what the program is doing on the first place because you made assumptions when you started.
And it's perfectly fine to initally learn that `.each { |name| … }` as a concept for looping over something.
> That's OK if you need a quick solution for a small problem, but it's not when your goal in itself is to learn how to program (in Ruby)...
I'm not sure what kind of new beginners you have taught (or how you learned yourself), but I always find that new beginners rarely know the whole picture anyway. There's nothing wrong with learning techniques without knowing how they work. In fact, we do it all the time: I wouldn't explain metaclasses when teaching about class methods in Ruby (although they are just an example of methods on the metaclass). I learned basic rules of derivates and how you could use them before I learnt why the rules are true; which am I very thankful for as I wouldn't even get why you would muck around with "limit approaches zero".
> If you want to learn a programming language, you inherently need to understand it.
No, if you want to know a programming language, you inherently need to understand it. If you want to learn it you can either do it from top-down (learn about specific code, then learn how they understand), bottom-up (learn about basic concepts, then combine them to real programs) or anything in between.
I'm mentioning this as someone who learnt programming top-down and spent years without really understanding anything. But I had fun! I created stuff! It would be a sad world if you had to understand everything before you were "allowed" to use it.
> The discussion is basically that a form of writing a specific block of code is easier to explain than another, although the latter is more widely used and considered the correct. Readability, though important, has little to do with choosing the correct construct to teach as to provide a good balance of the time and effort you need to put into learning.
Remember that the user has to learn blocks anyway. It's not like blocks is an advanced feature that is only used in looping; it's everywhere in Ruby! The real comparison here is "teach for, teach blocks later" or "teach #each, teach rest of blocks later".
Regarding your second and third bullet points: As I've tried to explain before, there is a tremendous difference between someone reading "The Ruby Programming Language" and "The Ruby Way" trying to "learn" the Ruby programming language, and a person who goes from tutorial, to example, to tutorial, to built product. I can certainly relate to the latter since I started learning C and even a little Asm (thank you SoftIce) by myself when I was 12 thanks to a small stint in a cracking group. These days, though, after programming for around 17 years, when I want to use a new programming language I read on it's syntax, it's methodology, it's patterns... Let's do the analogy thing here: If you want to "learn" multiplication as a child, you have two options. Memorize the multiplication tables, since it's improbable that you'll need anything higher, or actually learn the mechanics of multiplication and how to carry numbers up to the next order of magnitude. There are two completely different monsters. In one you learn what you need to do to create something specific. In the other you learn everything there is to learn about a topic to further your understanding of a topic. Learning the how and the why are two completely different things that get emphasized depending on you needs. My point here is that we're trying to discuss the best methodology for the teaching of a language. In a book. Generally meant to leave students with a complete understanding of the subject at matter.
A small aside, I didn't mean ruby blocks on that last section, I just meant block in the general sense. In any case, I totally agree on the last line you typed. And that's what I've been trying to get the OP to understand. I agree to teach each first because of a plethora of reasons I've already discussed here, but I don't agree Zed is a "troll" because he thinks the explanation of core concepts in his book should be more linear.
One could troll the for do construct example to require understanding of operational semantics, denotational semantics and / or abstract interpretation and gaulois connections, plus deriving the arithmetic rules in the calculus of constructions starting from the simple (sic) datatype definition Nat = Zero | Succ (Zero). All while ranting about the indoctrination in the evil ways of Turing machines.
The target audience of his book has just learned what a variable and an array is. Since the book as advertized as learning "the hard way," it seems to explicitly attempt to give an insight to what the computer is doing when you tell it to do something.
That is, I sure as hell could translate a for loop to psuedo-assembly, and I could probably teach a newbie how to do the same for their mental model. I don't even begin to think how I would teach them what the computer is doing conceptually.
The target audience of his book is not coding yet. What, should we require a comprehension and vocabulary test before they test something out in a REPL?
As such, I would say Zed's pedagogy is sound.
"Just memorize this syntax and do it" is not allowed.
> There is a function "each" that applies to arrays. "Each", for each element in the array, binds "elem" to the element value, then executes the body of the code block. The code applies "each" to array "arr" and executes "puts elem" for each element.
I think I would rather teach that #each is a method that iterates over the elements of the array calling the code block for every element.
This only requires you to teach variables, arrays, the if statement, goto/jumps and objects with their methods.
And frankly it being ruby, I don't think it's bad at all to teach objects and their methods, before teaching iterations.
Maybe it's not in line with your hard way philosophy, but explaining for loops with goto/jumps seems a bit abstract to me. Don't you need to explain how instructions are mapped to memory addresses and the instruction register to really do so?
An each method can be explained with a simple recursive function, no need to dive into hocus pocus CPU details.
I assume you mean "concrete" - goto/jumps is how it's done in the machine code; to get less abstract we'd have to start looking at how the processor is built.
When you're teaching goto/jumps you'd have to either teach another programming language (raw instructions) which would make it more concrete, but would also raise a lot more questions about how Ruby works, or teach goto in terms of Ruby code, which would make it fairly abstract :P
I'm saying teaching [idioms] as if they are the universal truths is bad. I teach them too, but I teach them as if they are just arbitrary bullshit you need in order to code with other people who have been indoctrinated.
While I agree with the general argument Zed's making, I think it's important not to create a false dichotomy where an idiom is either a universal truth or little more than arbitrary bullshit.
There are legitimate reasons to prefer constructs like "each" over for loops and it would be a mistake to let that get lost in the noise.
Here's my explanation: the human brain is an identity machine. Apply it intensely to anything over time and you can't help but identify with what you're doing and how. As soon as identity is involved the complete human package kicks in, including emotion and tribalism (banding together with those of like identity in opposition to those of unlike identity). Opposite ways of doing things begin to seem like threats, criticism evokes defensive feelings and so on. If you think you don't function this way, try paying closer attention.
You'd be on more solid ground (and closer to your point about loops) to say that, when teaching foreign languages to beginners, idioms are best avoided because the student is struggling just to know the individual words. Even then, though, I doubt I ever took an introductory language class that didn't teach at least some idioms; you can't get a feel for a language (or fully understand any real-world text) without them.
Maybe from all those classic books on writing, like the "Chicago Manual of Style", "On Writing Well" and "The Elements of Style", which all agree on the point.
Where you got this bizarro impression that idioms are NOT to be avoided in writing? Idioms are cliches, and they needlessly obfuscate a text.
My impression comes from observing language: it is saturated with idioms. You can't throw them all out (without a lot of unnatural effort) nor is there any need to. You might as well say that a musician should use only notes and not chords, or avoid standard chord progressions. If you can find any significant piece of writing that avoids idioms, I'd like to see it.
Idioms are not cliches. An idiom is a phrase whose meaning is more than the sum of its words. A cliche is a phrase that has become hackneyed through overuse. (Nor, by the way, are cliches necessarily obfuscatory. "Get your ducks in a row" is, but "Live and learn" is not.)
Style manuals have their place but are hardly scripture, and there are countless examples of great writers breaking every rule in the book. Indeed, Strunk & White breaks its own rules, sometimes when describing the very rule it's breaking (which is part of its charm [1]). So even if your claim were correct, it wouldn't prove anything. But I'm pretty sure it isn't correct. Since you claim that Chicago, Zinsser, and Strunk & White all agree on the point, please show us where; I'd like to see. All three of those texts are searchable online and I couldn't come up with anything. But I only tried for a few minutes.
[1] Just for fun, here is an example: "Students of the language will argue that try and has won through and become idiom. Indeed it has, and it is relaxed and acceptable. But try to is precise, and when you are writing formal prose, try and write try to." How can anyone not love E.B. White?
So just as reading the simply Yacc grammar for MRI Ruby is not a particularly great plan for mastering Ruby, learning the rules of grammar and memorizing individual vocabulary words isn't going to give you convincing conversational mastery of a spoken language.
Breaking a rule ocassionally is not the same as not having the rule on the first place. For one, they can "break their own rule" precisely because the rule exists. They break the rules they set with caution and only when the feel it is needed. That's the way it is with most rules, about writing or anything else. The rule provide a guideline for the 90% of times.
To me it seems like there are two consequences.
1) It's a huge burden on the beginner to be effectively aware of all the possibilities of doing things (like looping) in a language, and why one should be used where. In general, when a beginner picks up a language, he should be doing stuff and recognizing the trade-offs of what he's doing. Thus, he culminates critical awareness through choices and reflection of those choices.
I just can't imagine myself learning Python and hearing about doing things in a hundred different non-Pythonic ways. Unless I were to do it one way, and then later discover of my own accord that there is another way I can do it which has its associated cost/benefits.
2) If these "social mores" are indoctrination, and indoctrination is taboo, what if beginners stop writing .each loops? What if Python programmers start writing "magical" code? etc.
Wouldn't these Python programmers fragment the community and alienate/isolated themselves from a community which has firmly established conventions (for good reasons).
Either way, I love that this point has been raised. I've been looking into Ruby especially, and am always fascinated about how different the conventions and styles are.
Looping over a collection object requires some knowledge of its internal state (length in the case of a simple linear structure like Array). In most cases where we use for loops, that information isn't really relevant. There is no need to expose it to the outside world and break encapsulation.
Being consistent in using #each helps when you have complex/custom collection objects where iteration can't be done through a simple for loop. The object might not want to expose its underlying collection and hence can't provide a suitable way to use a for loop on it. Or maybe the collection object has dead elements that it wants to skip and so on. There are any number of reasons to hide the implementation details of the iteration from the external world.
Given this and since Ruby has a powerful block syntax, favouring #each fanatically over for loops, IMHO, is a good thing.
EDIT: I'm also guilty of accusing LRTH with the 'not idiomatic enough' charge in a blogpost I recently wrote for Ruby beginners at http://www.jasimabasheer.com/posts/meta_introduction_to_ruby....
The examples from the article don't make me think I need length or any other internal knowledge to use the for..in loop as opposed to each.
However, the other argument still holds true: In case of a user-defined collection object, 'for..in' will not work since the underlying collection will be hidden.
To my mind, anyway, using each/collect/inject expresses intent much more clearly than a for loop. If a clearer approach is available, why not take advantage of it?
However, Ruby uses the methods you mention, "collect" and "inject", and these in turn have a few synonyms. And as best I can tell, Ruby's 'filter' variant, 'find' collides with ActiveRecord. What's the general community take on these functions? Which name is commonly used, and how do you do filter in Rails?
Most of them come from Smalltalk's Iterable protocol[0]: that's the source for `select:aBlock`, `collect:aBlock`, `inject:thisValue into:binaryBlock` (Smalltalk uses `fold` when there's no starting value, and `inject` as "fold by injecting initial value"), `detect:aBlock` and `reject:aBlock`.
Smalltalk also uses `do:aBlock` where Ruby uses `each`, likely due to `do` being a keyword in Ruby.
Also, `find` is not `filter` (that's `select`, aliased to `find_all`). `find` is also `find` in Haskell[1] (and maps to Smalltalk's `detect`, also useable in Ruby).
> 'find' collides with ActiveRecord
Uh... no, ActiveRecord collides with Enumerable, I'm pretty damn sure Enumerable came first.
> how do you do filter in Rails?
What's wrong with #find? Or #where? Or #find_by_*? Same name, different namespace, different meaning. Kind-of the point.
[0] http://www.gnu.org/software/smalltalk/manual-base/html_node/...
[1] http://hackage.haskell.org/packages/archive/base/latest/doc/...
The key is 'natural to a language'. Some writers assume they're helping teach a programming language by teaching what the community regards as the most 'natural' solution to a given problem in order to avoid bad habits. In this sense, 'idiomatic' meaning 'using, containing, or denoting expressions that are natural to a native speaker' is an extremely useful phrase because it implies that a native speaker is an experienced programmer who understands both how to use the language effectively and how the wider community uses it.
'Mores' implies the use of a custom by a social group that may or may not be useful. However, in this case, 'each' is an idiomatic use of the Ruby language. The fact it's awkward to teach what makes 'each' work doesn't detract from the fact that it's widely regarded as a better way to iterate over enumerable collections. To me, the fact it draws on such a large portion of the language may indicate that those parts are actually incredibly useful, and in combination can result in succinct and expressive forms that become easy to work with after a certain amount of education.
The distinction between what's difficult to teach and what becomes natural after a certain amount of education is extremely interesting. I suspect accomplished Ruby programmers originally took a leap of faith and just adopted 'each' because they were told to, then later on fully appreciated the underlying implementation. That doesn't make it less idiomatic, it just makes it more difficult to teach.
As a Smalltalker, I think select/find/collect/each to be wonderful things, as they are of the same spirit as the do: collect: select: reject: methods in the Smalltalk Collection methods. That said, I find the notion of stigma attached to certain constructs to be counter-productive. In my experience, such attitudes are more about socially scoring points and vying for social dominance than they are actually about improving the community's code. However you get the functionality done is fine, so long as it's short and understandable.
(Such stigma attached to situations where people could use inject:into: but didn't was often counterproductive to readaibility in the Smalltalk community.)
My understanding of software engineering and computer science is that we should design with a clear mental pattern of what we need to build to get something done. This way, if there is some bug, we can trace it back in the design and not in the code. If we designed with the language in mind, then our software would be limited by the language itself. Then the bugs would just be harder to squash, because we now have to adapt our troubleshooting skills to the language and not the problem. In practice, anytime I face some bug, I just sit back and think about the design and not about the languge/syntax/idioms. It commonly takes me but a few minutes to mentally trace back the problem to the source. No debugger needed.
Zed, this is a fantastic piece, and I hope you include it as part of your books. Since you have shown to have such fine understanding of the problems begineers face, you should write a little booklet that should include things like the one mentioned here. Thank you for this post.
The useful content of the article is overshadowed by the vainglorious hyperbole.
In fact, don't take my word for it, here's Dave Thomas questioning the .each requirement:
http://video2012.scotlandonrails.com/D2_ML_07-Ruby1280_b.mp4
An "each" block has the scoping you'd expect, and is more consistent with the rest of the Ruby looping constructs (like select/filter or collect/map). It's not a huge deal, but there definitely are technical reasons not to use it.
Could you perhaps provide an example of code using .each that does the wrong thing in 1.8, but the right thing in 1.9?
a = 0
[1, 2, 3].each { |a| }
a # => 3In smalltalk there are no loops and no ifs... just methods that are executed on a condition. So 'each:' isn't any more complicated than a 'whileTrue:'.
Just take a look at this Smalltalk examples[1]: ----- [ condition ] whileFalse: [ statements ] [ condition ] ifTrue: [ statements ]
aCollection do: [ :var1 :var2 | statements ] ----
[1] http://www.cincomsmalltalk.com/tutorials/version7/tutorial1/...
I think that "idiomatic" is correct, as applied to the behavior of programmers broadly. Groups will fall into using the language in particular ways to indicate things that aren't expressly encoded in the language spec. This will frequently happen along language lines but can also happen within a company or a project.
I think that some communities, in particular the Ruby and Python communities, additionally have strong social mores against writing non-idiomatic code.
The most important thing you will ever learn about conditional loops is that effectively they are all shorthand for combinations of compare statements and conditional jumps in assembly. Past that really, none are more conceptually simple than any other...
So the analogy of 'each' being "piece of cake" to 'for' being "easy" doesn't really hold to me. Nor does the extreme list of 'concepts I'd have to teach to show someone how .each... works'. They both effectively are the same level of abstraction from 'CMP' and 'J__'.
As someone who learned ruby as my first real language, 'each' made a lot of sense to me. It uses fewer words and relates more closely to the english language than 'for...' to my mind. I don't want to say this is the case for everybody, and 'for...' definitely at least has one advantage (cross language use - though I'm not sure this should be of major importance in a ruby tutorial).
But to me this page doesn't really present a convincing argument against the teaching of 'each' before 'for...'
On the other hand, I agree with Zed that for is a better abstraction than each, and I disagree that each and for are at the same level of abstraction. If you're approaching it in the abstract, sure, they're both pretty high-level. But each is as close to pure functional as you get and for is plain iterative.
And assembly is iterative, so I'd call for better for giving people a sense of how the metal works. Which, I think, is important for newbies.
Yes. Its a bit of a side issue and the OP hardly mentioned it, so I thought I'd ignore it. I can see how in this way you then might see 'each' as more 'abstract', but this difference doesn't back up the OP's main points (eg. the "piece of cake"/"easy" analogy).
> On the other hand, I agree with Zed that for is a better abstraction than each, and I disagree that each and for are at the same level of abstraction. If you're approaching it in the abstract, sure, they're both pretty high-level. But each is as close to pure functional as you get and for is plain iterative.
Firstly, how one abstraction can implicitly be a 'better abstraction' than another I am not sure. Only perhaps because you (individually) understand it better or have used it before it is more useful to you.
Secondly, how is 'each' any less 'plain iterative' than 'for...' when they both do exactly the same thing (iterate)? Also, how is it more 'functional'? To me its more object-oriented in that the object is stated first, but this isn't to say it is less plain or more abstract.
> And assembly is iterative, so I'd call for better for giving people a sense of how the metal works. Which, I think, is important for newbies.
I agree that Assembly is good to learn, but not sure what you mean by calling it 'iterative'.
That was clumsy of me. I meant to say that it was a better abstraction for teaching iterative programming, which is a good tool for getting people productive with a minimum of training.
> Secondly, how is 'each' any less 'plain iterative' than 'for...' when they both do exactly the same thing (iterate)? Also, how is it more 'functional'? To me its more object-oriented in that the object is stated first, but this isn't to say it is less plain or more abstract.
When we're talking rich objects instead of simple iterators, we are speaking more abstractly. Now, naturally, you're always using a rich object in a language like Ruby, but it's a lot easier to gloss over that with for.
>I agree that Assembly is good to learn, but not sure what you mean by calling it 'iterative'.
It's full of side effects, you're not working with clean encapsulated objects.
> Secondly, how is 'each' any less 'plain iterative' than 'for...' when they both do exactly the same thing (iterate)? Also, how is it more 'functional'? To me its more object-oriented in that the object is stated first, but this isn't to say it is less plain or more abstract.
More object oriented == more abstract. But that's basically a semantic game and kind of beside the point, which is that there are more principles of computer science lurking in each than in for. That's great if you're teaching computer science, not so much if you're teaching computer programming.
also - maybe you mean 'imperative' rather than 'iterative'?
In terms of 'abstract', I can see where you are coming from that there are more underlying principles and its more abstract from this point of view. From the point of view I was at, they are both referencing the same variables doing the same thing so its the same abstraction from a programmatic(?) point of view. Probably a poor/confusing choice of words on my part...
A novice needs to learn the most practical and correct way to do things. An experienced developer needs to learn to transpose his current knowledge to a new syntax. Both benefit from being taught the most common or correct syntaxes and patterns.
and HN discussion: http://news.ycombinator.com/item?id=565528
It may be the case that at any given point, the idiomatic way is not the fastest. However, if idioms are widely adopted, the language implementers have good reason to optimize them. I'm not saying that's what happened in this case, but I think it's reasonable claim to make in general.
Huh? That is definitely not what it means. That may be a byproduct if the language implementers then make the social mores they admire the ones that perform the best, but otherwise that's not what makes something an idiom or a social more.
I understand the definition of the word idiom. I'm saying that the quoted scenario is, in fact, not just hypothetical, but likely. Thus, following idiomatic style can help one avoid performance issues.
for (;;) {
}
to mean an endless loop. This really is an idiom - to people who've "grown up" with it, it says "endless loop" as clear as day, but for everyone else it just looks odd. And C programmers who know the idiom don't tend to look down on someone who instead uses: while (1) {
}
or some other form.I feel it's definitely a good idea to ensure the reader is introduced to the .each early though. I guess it's taken for granted now as lots of languages have added their own versions of them, however for myself, coming from a language that doesn't have blocks it's a really good way to start to understand how they work.
Did K&R explain everything about the preprocessor with their "Hello, world!" example? Or the implications of different return codes from main(), what 'return' from a function even means, etc?
Because you aren't teaching, and it merely becomes rote memorization. After all, what does || really mean? What does it do there? Without explaining it, it's magic. And instead of learning, the student must disassociate || with what it really and merely associate it with each.
On top of this, you'll have those wondering what || is, and bothered that they are using something without understanding it's importance.
> Did K&R explain everything about the preprocessor with their "Hello, world!"
No, but they explained what that line (#include <stdio.h>) did. They didn't just gloss over it. They covered every line, and every element, including the different braces.
So, either you teach what || is, or you ignore it, and that will only make things more difficult later on.
That is again another arbitrary social more invented by a rant written by Dijkstra. Learning that a computer processes code by using jumps is an important concept to learn, and it's actually a useful technique in many languages. Saying someone should never learn them is just stupid.
As a side effect (haha) I realized that a) a monad is very interesting and useful concept, no matter in which language; b) Haskell "cheats" heavily in order to be able to do I/O (and side effects) and c) it's this cheating (ie. IO part of IO Monad) which makes it hard to understand (I really prefer Clean's approach, where you get explicit 'world' object to manipulate). As a result I don't know when or even if I'll learn Haskell - and it's caused solely by the lack of full, up-front explanation of Haskell I/O model in most (all?) learning materials for the language.
So, for me, yes - you need to teach me "theoretical grounding of monads" and more or you'll lose me. I never read Zed's books, but I think he's targeting people like me, so your criticism is invalid.
"As a result I don't know when or even if I'll learn Haskell - and it's caused solely by the lack of full, up-front explanation of Haskell I/O model in most (all?) learning materials for the language."
The IO monad is just like the State monad, except you can't get the State back out. You're passing around RealWorld. (In any event the "theoretical grounding of monads" doesn't tell you anything about IO. It would tell you things about monads in general---monadic bind and return and join, etc. Haskell may be doing weird things with its IO monad, but your objection actually bypasses what I was talking about, which is the category theory shee that Haskell is known for, but is also found in, say, Scala, where IO is not done through a special monad.)
ETA: given that these books are apparently for people who might need to be taught about variables in the first place, I wonder how accurate your conception of how like you they are is, and how much that resemblance applies to the case at hand. To reiterate, I can't really understand why any explanation that would pass muster with Zed for adequately explaining how "for foo in bar" works in Ruby, including the binding of the name foo, the repeated execution of the body, etc., couldn't be straightforwardly adapted to "bar.each { |foo| ... }". Would the adaptation not get into the weeds of the .each method? Indeed it would not. But then, the explanation of "for ... each" also didn't get into the weeds of that. Both should be acceptable if either is.
>You aren't creating programmers, you're creating good little Rubyists.
You should be creating good Rubyists, if you're using Ruby to teach programming.
People probably shouldn't confuse them. Does Ruby actually have a looping function?
These are all relevant questions when determining if it is a function or not.
Just the simple fact that loop control is determined by a boolean and requires state implies that for isn't really a function.
For what it's worth, I think Zed has a point. If you're trying to introduce a broad array of concepts to a new a programmer, going with a mapping method associated with an iterable is kind of complicated if you want to explain the whole shebang.
Compare map from Haskell, Lisp, Python etc with each. In each of those languages, you only need to introduce:
1) Named values (as variables, or not) 2) Functions 3) Lists 4) The idea that a function can take a function as an argument
You do need all the additional stuff for each that he mentions.
Similarly, in languages that make higher order functions more difficult, and have implicit state, for is an easier thing to explain.
And loop, of course.
Actually, the singular is "mos".
<ducks>
I think your comparison between for loops and .each is flawed - clearly you don't need to explain all those concepts in detail if at all. A for loop ultimately involves some level of polymorphism on the target collection, at least to the same level as required by the call to .each. I'd say that conceptually they are very similar, with both constructs having specific differentiating features.
Also, .each doesn't eventually become a for-loop in C code. 1/ Neither Ruby nor Python are compiled to C code, and 2/ I wouldn't expect that the respective looping constructs become a typical C for loop, it is more likely they will be implemented in the native op-code (e.g. CPython, Ruby 1.9).
With respect to why .each is fundamental to Ruby, we clearly see some key differences between Python and Ruby in this respect. I think the critical thing is that Ruby tends towards being a functional language while Python tends to be more imperative. We can see this clearly in the types of operations provided by Python, for example, list.reverse() in Ruby returns a copy of the list in reverse order, but in Python it reverses the list in place.
Using .each in Ruby code leads to shorter more concise code in contrast to the corresponding Python code. Whether you feel that you are being judged or not (in a social sense) is probably a matter of who you choose to interact with on that level (e.g. a particular developer community). Have you ever had to use non-idomatic C code? How about non-idomatic Python code? Clearly, a big issue with source code is composition (on a project level) and non-idiomatic code causes many issues in this area.
It is also interesting to note that non-idomatic behaviour plays a role in larger society. For example, choose the GPL and you might receive some complaints from communities that typically use MIT or BSD license. How about real life? If your friend buys you a drink and you don't play the social "ball game" (e.g. behave non-idomatically) you might have social problems in that area too.
I think that ultimately you are confusing a social interaction with a programming language best practice. I don't think either of these things are bad - heck, look at all the company specific coding standards for C or C++ code - but ultimately whether you choose to indoctrinate your students into a particular social circle, or teach them the best practice for a given programming language is simply a matter of perspective; and empowering your students to understand why things are the way they are, rather than simply saying this is the way we've always done things, is ultimately something I think is very important.
Thoroughly agree with the crux of the argument, though. There is too much cargo cult science wih regards to programming practice. eg don't ever use gotos, upper case reserved words in SQL, functions should be no bigger than N lines. This type of thinking cripples our critical faculties, and is to be resisted.
Concepts needed to teach recursion: Function calls, if-statement