var totals = []
for(var i=0; i<orders.length; i++)
totals.push(orders[i].total);
Now... maybe "splat" and "get" are "jargon", or maybe they're "abstractions" or even "explicit" (frankly I didn't understand that stuff)... But I'll bet good money that bugs in or near this version get fixed faster in the real world. And I'll even bet that truth holds for people who happen to know what "splat" and "get" are supposed to mean.
I remember when I would've agreed with you that your version is easier to understand and better, but now I don't.
I think it's javascript's fault. If you were working on a lisp codebase (suspend your disbelief for a second for me, please), you might see something like:
((splat (get 'title')) orders)
You might consider it perfectly appropriate in context, especially since you probably already have a good idea what splat and get do.
If you were working in Java and saw
int totals[] = splat(get("title")).call(orders);
You'd probably be upset at whoever wrote it, and with good reason. The difference between legitimate jargon and cleverness is contextual.
The argument you guys are having arises because some people think of javascript as a dynamic sort of java with lots of weird stuff thrown in, and some people, ragenwald included, think of it as a lisp in a java costume. I don't think either view is entirely correct, but both are reasonable and you can write your javascript either way.
When it comes to "clever" vs "explicit", I'd say it comes down to the opinions of all the people who have to maintain your code: are they expecting lisp or java?
More than once I have found about a new technique in language A and thought: "Wow, this is clever". Then sometime later I learnt language B where that technique is not only common place, but has some nice syntax sugar and is used throughout the whole standard library. And that makes a huge difference in the perceived cleverness of the code.
I remember this exact argument when Ruby first became popular and the PickAxe book was telling people how to write .each and .map instead of loops.
Come to think of it, I remember this exact argument when SmallTalk was introduced.
My elders probably remember having this argument when discussing LISP vs. Fortran.
I'm not sure what I can tell you that hasn't already been said in the sixty years that programmers have been having the whole "It's easier to understand when you're directly manipulating the metal" argument.
If you're sick of having the argument, then why did you troll it? :)
I mean, I'm perfectly willing to be your imperative curmudgeon strawman if you really want. But honestly, if you're going to blog about the spectrum of "explicit" programming, you're sort of morally bound to accept arguments on sides of the spectrum you'd rather not talk about.
If you want that argument, let's start with the fact that I can make Raganwald's version work, unchanged, with the data structure changed to be the values of a hash, the keys of a hash, a tree data structure, and a streamed data set that you do not entirely possess at any single time. Your version requires it to be an array, and nothing else. You may retort that it almost always is an array. My experience in languages that support the abstraction disagrees with you.
Secondly with your version I find that I require significantly more typing, and get it wrong significantly more wrong simply because I had a trivial typo.
Thirdly with your version on a double loop I find myself periodically messing up because I slip in the wrong counter variable in the wrong place. Stupid error, but the code compiles and does the wrong thing, then I have to track it down. With the functional version I pretty much never make that mistake on that use case.
And finally my experience, with my background, says that it is easier to fix mistakes in the functional version than the explicit one.
The result is that separating the concept of iteration from the implementation is a good thing. Even C++ realized this fact (badly) with iterators.
> Even C++ realized this fact (badly) with iterators.
would you mind clarifying that a bit (or a bunch) ? afaik, STL cleanly separates the notion of structures and algorithms that operate on those structures via iterators.
I'm not the grandparent poster, but one classic problem with STL iterators is that they always have to come in pairs. If you think about it, most STL algorithms don't really work on iterators, they work on ranges that happen to be defined by pairs of iterators. That makes it hard to compose operations, makes it hard to work with infinite streams, and makes it possible to screw up and write something like std::transform(v1.begin(), v2.end()).
Have a look at Alexandrescu's work on ranges in D for a more modern exploration of the same concept.
What is wrong with STL iterators? To see the shortcomings, let's dream up a better one and then compare.
Our hypothetical interface to iterators will have the following methods:
bool not_done(); // Is the iterator done?
void next(); // Move to the next value. Throws exception if done.
Foo value(); // What value do we have? Throws exception if done.
Our data structures will support the method iterator() that is the moral equivalent of begin().
We already have the equivalent of STL iterators. You just change:
for (data_structure::iterator it = foo.begin(); it != foo.end(); ++it) { /* do stuff with it / }
to
for (iterator< fooish > it = foo.iterator(); it.not_done(); it.next()) { /* do stuff with it.value() */ }
So far uninteresting. But with this new interface we can add four interesting templates in a straightforward matter.
1. Take an iterator< fooish > and a function that maps things of type fooish to type barish, then produce an iterator< barish >.
2. Take an iterator< fooish >, and a function that maps fooish to bool, then produces an iterator that gives you just the ones that mapped to true.
3. (Generalization of the first two.) Take an iterator that produces things of type fooish, and a function that maps fooish to iterator< barish >, and returns an iterator< barish > that just maps each value from the first to all of the bars that it produces.
4. Take an object with a method that produces things of type fooish, and produce an iterator< fooish >.
These are all simple to define given this interface. What do they allow us to do? Well the article that introduced me to the idea of iterators was http://perl.plover.com/Stream/stream.html - absolutely everything in that article is straightforward with the interface that I described and absolutely none of it is easy with STL iterators. (It is all still actually possible, but tricky. Proving that fact is left as an exercise to the reader, at which point you'll know why people don't tend to think about problems that way in C++.)
1. I'm not sick of it, I just can't tell you anything you haven't already heard and rejected.
2. I'm not trolling, just pointing out that this has been going on for a while, which implies that there are reasonable people on both sides of the discussion.
3. I never argued that explicit is bad or that jargon is good, I just explained that sometimes, jargon accomplishes a purpose.
Thank you for this. I didn't see your article as trolling.
You articulate a very valid point - The degree of perceived "cleverness" in code is dependent on the reader's level of familiarity of the language's basic and advanced concepts.
The code suggested by ajross seems more readable not just because it's closer to the metal, but also because reading it doesn't require memorizing many ad-hoc names like "splat" and "pluck", you just apply a few logical rules that you learned once. But there's no reason why code written with advanced abstractions couldn't be "locally readable" in the same way...
The problem is that you have to run the code in your head to understand what it means. It obfuscates the intent. Just read it aloud and you'll see what I mean,
"Once upon a time there was a variable named, totals that was initialized to an empty array. Next, a for loop happened and some var, i appeared (his name doesn't really matter.) i was initialized to 0. Enter stage right, totals's buddy, orders. orders has a property named length. i was told that it shall be incremented by 1 so long as i was less than order's length property. Sadly, the for loop was just using i. i didn't know it would be disregarded once for loop was finished.
Not surprisingly, what happened next was quite remarkable. The total property of orders index i lept onto totals. i was incremented. Then the total property at index i lept onto totals... i was incremented... Finally i was greater than or equal to orders.length, so the for loop ended. totals lived on, changed by its journey while i was never seen or heard from again.
Study guide question: how did totals's character change by the end of the story and what did this mean?"
"
The following code snippet means the same thing, but it's also self-documenting. Plus there's less accidental complexity:
var totals = orders.map(function(order){
return order.total;
});
Read it aloud and you'll see what I mean,
"Once upon a time, there was a var named, totals. It was a list of order totals. Fin."
> map is a function which, for each item in a list, in order, executes a function on that item and populates a new list with the returned value of that function.
(I'm sure that description is technically wrong or misstated on some level.)
And that goes back to the original post. map is jargon, meaning basically what I posted above. If you don't know the jargon, it looks a lot like obfuscating cleverness.
Your example is great, but it would be even more convincing if it used list comprehension syntax such as
totals = [order.total for order in orders]
(like some other poster in this thread did)
Then you don't even need to know what "map" means, you only need to know "for" (well, maybe you would need an insight that "order" is defined after its use, but I digress)
Frankly, I would like to have more code example parties.
Too often, differences between programming languages are treated as strictly philosophical arguments. Comparing and contrasting actual code is far more interesting, in my opinion.
myself. I really wish was as much progress on syntaxes for expressing relational algebra as there is for the functional stuff. Dealing with map and apply seems a bit fiddly and low-level for what should be a straightforward thing to express declaratively.
Here are some interesting non-macro variants, first in Rebol:
select: func [block] [
totals: []
parse block [
set this word!
'from
set coll word!
(totals: map-each n (get coll) [get in n this])
]
totals
]
; then later...
select [total from orders]
;; nb. `select` is a core function provided by Rebol
;; so remember this example overwrites that
;; (in this scope/context) :)
;;
;; Alternative `dialect` to strive for would be..
;;
;; doMap [select total from orders]
And also in Io:
select := method(
msg := call argAt(0)
this := msg name
coll := msg next next name
newMsg := message(COLL map) // COLL just a placeholder message
newMsg setName(coll) // Now been changed to correct var provided
newMsg next appendArg(this asMessage)
call sender doMessage(newMsg)
)
# then later...
select(total from orders)
Both examples work at runtime. However in Io you can also amend the AST directly.
... Where :total is a symbol, and & is asking :total for the Proc version of itself (a Proc being an anonymous function of sorts), and map is then calling that Proc with a single argument (an order from the list of orders).
The c# syntax, myNewList = (from i in myList where i > 3 && i != 7 select i * 4).toList(), is nice too. Similar to using LINQ, myList.Where(i => i > 3), but I don't believe that form allows an easy use of the i * 4 part. One annoyance I have with .NET is there are too many types that are similar-but-not-quite a List<>, making me do conversions often. At least it's usually not much more than a .ToList(), except in the case of the controls in a Windows Form, which are their own weird list-type structure that doesn't support that.
It's all dependent on what you are familiar and comfortable with. You, apparently, are familiar with and feel comfortable using C style for loops over collections to sum an attribute. In Perl I would write your example as:
my @totals = map { $_->{total} } @orders;
Does that make Perl better or worse in any way because of that? No, it's simple the idiomatic way to achieve that result given the current environment.
What we are really doing, is mapping a concept to the language. The concept in this case can be expressed in a few ways, but I would express it as:
Collect the total for each order.
I don't expect programmers to try to match the structure of how that was expressed in English perfectly in their language/environment, but I DO expect them to map it into the idiomatic way to do it in that environment.
The key point is the environment. If I'm programming in perl, I expect others that will add or modify my code to be familiar with perl. If I'm also using Mojolicious, I expect others that are tasked to work on that code to be familiar with Mojolicious as well, or at least willing to look up resources to try to figure it out. In short, I expect familiarity with the chosen tools. If that's not a valid assumption, then those tools should not be in use.
Similarly, I don't feel the need to dumb down my vocabulary or concepts for HN, but I might for a different audience.
The problem with loops in javascript is that, once you start doing more complicated things than accessing array indices, you run the risk of introducing bugs that don't exist when using a more functional style.
Once you're using closures in your for-loop to prevent everything referring to the last index in the array, you might as well be using map and reduce and all that. Otherwise you end up wasting a lot of time to come up with stuff roughly like this: