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:
Code needs to read like good copywriting: obvious to the reader as if they were the author. Good copywriting needs to be aware of the target audience and the idioms they use.
Abstractions and jargon come along with the audience. It's critical to write for your target audience.
If you work with people who think computers are proof verifiers, splat is probably best for you. Then again, you should probably be writing it in Haskell/Fay/ML/...
If you work with people who think computers are shufflers of bits, then maybe a for loop is best.
And if you work with people who think computers are consumers of over-specified utility libraries, perhaps there's a iterator subclass implementation to be made.
> write for your target audience [and the rest of your comment]
No.
Good writers write good copies that are good for (almost) any target audience.
This applies to code too.
For me archetypal "good python code" examples can be found in http://norvig.com/, eg http://norvig.com/lispy.html and it doesn't care about abstractions, jargon, splat, and other things.
Obscure jargon and custom methods are always going to be perceived as clever.
Personally I don't think this
var totals = splat(get('total'))(orders);
is an improvement over
var totals = _.pluck(orders, 'total');
Why? Because the latter has a single layer of abstraction - what does `pluck` do? - while for the former you have to learn about `splat`, `get` and their internal behaviour/return values, specially if my jargon is not exactly the same as this library's jargon.
We've had the exact same discussion over Promises: people favor a slightly less convenient syntax over too many layers of abstraction. If you have a large codebase where the functional methods are really going to help, great, go on and put a note on the readme to guide other developers before they encounter it; but unless a specific implementation/library becomes hugely popular it's not going to fare well in public or small projects, and will be seen as clever, opaque code.
edit: sorry, I misread the 'is a small incremental improvement' as referring to the underscore method. Keeping the post for discussion anyway.
The post doesn't say it is an improvement, it says that Underscore is deservedly popular and also that both are jargon.
UPDATE: I will probably clarify that in the code. But if you want to open the door into my madness, the use case for "splat" is that it's a combinator, a function that takes a function and returns a function. So if you like "pluck" as I do, you write:
var pluck = compose(splat, get);
// ...
var totals = pluck('total')(orders);
And you wouldn't really write `splat(get('total'))` either.
Having building blocks that are combinators rather than general-purpose functions is itself a family of jargon. It's a win if you do a lot of method decoration and function composition, otherwise it's...
Re: Promises: According to the last article that was posted here, the point of Promises is to provide greater semantic information that assists in organizing the dependencies and prevents subtle race conditions, it is not simply syntactic sugar on callbacks. I'm not prepared to defend that position, but the article made a compelling case.
Yes, that was a great read. What I mean is that the overhead of understanding and keeping the abstractions in your head (promise object, resolve/defer/reject, returned promises, chaining) almost nullifies the advantages it offers. I think promises are great in some circumstances, but unless they catch on as a standard way of working with async calls, they will remain just a 'clever' thing you can use, but most developers won't understand, and will make your code incompatible with everyone else's. It's a chicken and egg problem.
I don't really see the method as being any less functional than the function version (ie, they're both without side effects, the method version just has 'this' as an implicit parameter).
Though I guess that's just a difference in perspective.
Edit: Having stumbled upon something else you wrote[1](Viz. "WHy the crazy idea of using a splatter instead of a mapping method or function?"), I now understand the reason you're making the distinction. Though (speaking as someone who certainly doesn't have the level of expertise to be making this semantic argument) I really don't think it's the distinction between "functional" and "not functional" so much as... "composability"?
I think a lot of people would argue that composability is what "functional" code is really interested and many of the other things that are normally associated with it are actually side-effects of the emphasis on composition.
It seems like there's a learning spectrum that defines what's explicit and what's clever. If you're not familiar with HOF, then functional programming is always going to appear clever. But as you learn, things lose a bit of magic.
I think the key to a team being explicit vs clever together is that everyone has about the same understanding of explicit vs clever.
Cleverness for it's own sake may be a bad thing, but cleverness in general isn't bad.
The really important thing is that when you do something clever, you ensure that people who use your clever function/module/library/webservice don't have to also understand your cleverness.
Leaky abstractions can be worse than no abstraction.
This is a bit of a tangent though. With respect to the micro level cleverness mentioned in the article:
Simply naming the output of those helper functions would go a long way...
The ActiveRecord example, to me, is an example of "clever". Any code that exhibits "spooky action at a distance" is too clever, in my opinion. When I can't hand trace a method or function and see what's going on, it is too "clever" - or at least it isn't "explicit".
Spring in the Java world does this with it's XML configuration and code injection based on annotations. You can't decipher what's going on by code examination. To code in Spring you have to fully understand the Spring framework. Rails (to me at least) is similar. The steep learning curve makes them less hackable.
With Clojure, by contrast, you can always look at a function or macro and (eventually) grok what's happening.
Hah. I was with you up until the last line. Macros do exactly the same thing as spring-aop - they transform your function into some different code, so that when it's called it does something different to what was written.
/scala guy, very worried by the introduction of macros, despite (perhaps even because of) all the cool things they can do
Certainly macros can be hard to decipher, however they don't set up this spooky action at a distance, for example a annotation signaling bytecode injection. There's no way to hand-trace through that; the only solution is to learn the library / framework in-depth.
Honestly I don't see the difference. If you're stepping through in a debugger, you see the "real" code. If you're just reading the code, you read something that looks like a function and then have to jump far away to the annotation or macro to find out what actually happens. Assuming IDE integration it's really no easier or harder to jump to a spring aspect than it is to a macro.
Many annotations in Spring are merely markers used during startup to wire things up or generate bytecode on the fly. There is no way to jump to the actual code.
It's not the language, it's the reverse-eternal-september caused by people who are actual programmers with an actual background in computer science writing node applications on the server and client applications in the browser.
As long as it was just DHTML for doing some form validation or whatever, it was treated like a "scripting" language.
Either that, or the fumes of smugness are getting to people.
Clever is fine as long as things are somewhat self-explanatory. At least in this example, the get() method name is confusing. Get is a customary well known method name to extract a property of a map or object, and making the call now. The usage context in the example is that it is a parameterizes getter function that's passed in to splat to be called later. Perhaps it would be clearer if it's called getter('total') or accessor('total'). That shows it's building a parameterized actor function, and it can be passed around and called later.
In working with functional code, I prefer naming high order functions with an actor connotation.
Good question. Let's re-arrange the get method a bit to make it easier for currying demonstration.
function get (propertyName, object) {
return object[propertyName];
}
The method 'get' does what it's named, to get the property now in the call. Curry(get('age')) creates a separate entity that parameterizes get with the 'age' property. It's a new function object different from get. But at least the term curry(get('age')) gives some visual cue to the code readers as what it does vs just get('age').
For languages (e.g. Haskell) that implicitly curry everything, it's understood that any missing parameter means a curried function has been created and the actual call is deferred. Javascript is not such language. Mixing in the naming convention for implicit curry just causes confusion for the readers.
Javascript's currying is explicit with pattern such as, getter(get, 'age'), which would wrap a function around the get function with a closure {propertyName: 'age'}.
I think it's best to consider the audience and the convention of a language when naming things.
I wrote implementations of applyFirst and applyLast as I thought they should work and then merged them according to applyFirst(applyLast, map) and I really got the splat function as described in the next code block. My problem is I still don't get why applyFirst(applyLast, map) works. I'm feeling that all this return function(param1, param2) notation obscures actual flow.
Is there a notation for this that could make me understand it intuitively?
I'm not aware of specific notation, but let's first see the difference between map and splat:
map is a function that takes a list and a function and returns a list
map :: (function, list) -> list
splat is a function that takes a function and returns a function that takes a list and returns a list
splat :: (function)->((list)->list)
That is sort of effectively the reverse order of parameters for map. What applyFirst does is, it takes a function and a parameter and returns a function that takes more params and prepends the given one. applyLast is similar, but appends the given one. Let's walk through the application of applyFirst(applyLast, map)(fn)(list)
applyFirst(applyLast, map)(fn)(list)
applyLast(map, fn)(list) -- here applyFirst has supplied the map param to applyLast and fn is the given param
map(list, fn) -- and here applyLast as supplied the fn param to map, putting list first
If you supply more parameters, you'd get:
applyFirst(applyLast, map)(a, b)(c, d)
...
map(c, d, a, b)
Which wouldn't work with map per se, but shows how the applyFirst(applyLast, map) construction works.
After your excelent suggestions for names
(makeMapper and makeGetter, they make everything easier, thank you),
I'll try answering this, with a F#-like syntax using redundant parentheses
around arguments to make them more explicit
to those who don't know F#
Let's suppose map is defined as:
> let map (aList) (aFunction) = ...something here...
Read the above as: makeMapper is a function just like applyLast,
but the first argument (to applyLast) is already provided, being "map".
The functionality of applyFirst is implicit when the
remanining parameters are missing, so we only need applyLast.
So makeMapper is called as
> let mapperWithAGetter = makeMapper (aGetter)
This completes the call to applyLast,
so now we have a (fun (x) -> map (x) (aGetter))
Now we only need to bind x. The result is called like this
> mapperWithAGetter (aList)
IMO, all of this could've been written as:
> let makeMapper (aGetter) = fun (aList) -> map (aList) (aGetter)
Or:
> let makeMapper (aGetter) (aList) = map (aList) (aGetter)
Read both as: makeMapper takes aGetter and returns
a function which takes aList
Notice that everything would be much easiear if map took aFunction
as its first parameter:
> let map (aFunction) (aList) = ...
> let makeMapper = map // duh
Remember that partial application is implicit!
It would also be easier if applyLast had better syntax,
using _ for the missing parameter (the first), as in this
Scala-like snippet:
How 'explicit' something is has more to do with the social structures around a new technology. Does rails make it easy to understand how its conventions are applied? Does it assume that all rails programmers understand implementation details? It does not, and so it is fair to criticize it for magic. In principle you could solve the 'magic' problem without changing a line of code, purely by changing how the mechanisms provided are packaged to new programmers.
I spent some time last year trying to distinguish between abstractions and services. According to me, abstractions are things that you are expected to understand the workings of, and services are things you are not. By my definitions, rails is a service, but the lisp programming model is an abstraction, because it is impossible to gain a few months of experience with lisp without understanding in great detail how macros are expanded and so on. Many programming libraries are services because in practice nobody bothers to learn how they work, and we as a field, as a species, tolerate this cargo culting.
This is an interesting perspective if I'm understanding you correctly.
Are you saying that Rails is "magical" because it is often presented to new people that way, but if it were more often presented as "a bunch of Ruby code that does a bunch of stuff, please do go look and see how it does that stuff", it would not be "magical" without being implemented or documented at all differently?
Kinda, if I'm making sense of your question :) Did my article make sense? I'd love to hear what you thought of it. There's basically two halves:
1. Under-promising. I'll quibble with the word 'documented'. Part of the problem is that frameworks market and position themselves with their prose to be hermetically sealed containers offering wondrous features. That's part of their documentation. That we do need to change, IMO.
2. Over-delivering. It's not enough to be open source, and it's not enough to just say, "please do go look and see how it does that stuff." Software is in the stone ages because we aren't able to actively help newcomers get quickly up to speed on our code. We need to think about the big picture of a codebase, and how it's presented to others.
hey Scott, I just saw this. It depends on whether your map implementation expects a unary function or whether it injects optional additional arguments like the index, and whether your function is unary or accepts optional other arguments.
That's fine, as is _.map(orders, get('total')) or just plain map(orders, get('total')). As I explained in another comment, splat is a combinator, which makes it particularly easy to compose. Compare:
function pluck (list, propertyName) {
return _.map(list, get(propertyName));
};
to:
var pluck = compose(splat, get);
So yes, for that one line you should use map, but when making things out of the mapping construct, it is sometimes handy to use splat.
Other folks have introduced the idea that splat is really nothing more than flip(curry(map)). There's a good argument for better naming once you realize the underlying symmetry.
This article touches on it but I do find that there are different kinds of programmers. Generally, I find that an individual either likes to solve problems, i.e. write code that as obviously as possible gets the result they need OR they like to solve puzzles, i.e. write code that is the "best" way to do something and may not be obvious at all.
They generally have two different definitions of readability as well. The former thinks code is readable if it is obvious how it works and how to change it, that latter thinks it is readable if it happens to match how you would talk about it in english with no regard as to whether or not it is editable. Often the inability to edit (or even debug) stems from implicit magic vs explicit code.
That isn't really what I mean. I'm definitely OK with functional style collections and futures and the like. I'm annoyed with implicits, macros and other action at a distance language features that it make it hard to reason about something locally.
It's all relative. One persons old-hat jargon is another person's "needlessly clever" is another person's "too explicit". Reminds me of when I was dating, when girls were telling me they wanted someone who was "nice, but not TOO nice." Ok thanks lady, you apparently feel like you just communicated something to me, but you didn't really at all.
> You have correctly predicted in advance that the implementation will need to change and can do so without breaking the rest of the application.
Some abstractions allow you to avoid predicting things. 'map' is less restrictive than a for-loop if you don't need the powers of a for loop (store state between iterations etc..). So with some abstractions you can postpone restrictions that you don't need and get code that is easier to change.
This is probably all obvious but I thought it was important. It can probably be stated more succinctly.
> mapWith is the self-curried right partial application of the map function. If that seems like gobbledegook to you, it's because I'm using jargon.
Reginald say "jargon" as if he's proud of it, but for this example, the more pejorative meaning seems appropriate. Why not just say "curried map"? I was under the impression that's why the lambda is the first argument.
Random piece of code cannot be explicit or implicit. You have to specify exact thing you are talking about, like creating a function. You do create functions explicitly if you use map() and implicitly if you use splat(). But please don't describe the whole piece of code as explicit or implicit because of that.
I will think that code has an audience and it's audience should be explicit. As long as the expectations are correct, it will just be 'sufficiently analyzed magic' and not only sorcery.