Explicit vs. Clever
raganwald.com
raganwald.com
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.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.
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.
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.
4. I have written for loops and liked them.
So, 5. Don't assume enmity where there is none.
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.
Your example really highlights this.
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.
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.
thanks !
Have a look at Alexandrescu's work on ranges in D for a more modern exploration of the same concept.
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++.)
Thank you for clarifying.
[order.total for order in orders]
very easy to understand in terms of natural language. Still, now I've learned Haskell I'd prefer to write map (^.total) orders (map :total orders) select total from orders
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. (defmacro select [total _ coll]
(let [total (keyword total)]
`(map ~total ~coll)))
(select total from orders)
Problem solved!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.Too often, differences between programming languages are treated as strictly philosophical arguments. Comparing and contrasting actual code is far more interesting, in my opinion.
orders.map(&:total)
... 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).More detail: http://stackoverflow.com/a/1217114/3528
map *.total, @orders; # perl6
@orders.map: *.total; # perl6 OO
map {$_->total} @orders; # perl5
orders map(total) # Io
map-each n orders [n/total] ; Rebol totals = (order.total for order in orders)
totals = orders.map (o) -> o.total var values = from x in combobox.Items.Cast<ComboBoxItem>() where x.Value > 5 select x.Value;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?
(map :title orders)
Is analogous to: (map #(get % :title) orders)
Which is shorthand for: (map (fn [order] (get order :title)) orders)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.
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:
(function(order) {
// do something
})(orders[i])"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."
> It was a list of order totals.
You can only say this knowing (basically):
> 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.
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)
var i, order, totals = [];
for(; order=orders[i]; i++) totals.push(order.total); 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.
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.
Yes.
> 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.
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.
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...
Clever.
var totals = splat(get('total'))(orders);
is definitely nicer than var totals = orders.map(function(o){return o.total;});
But there really is not much difference between the coffeescript versions (actually, I'd argue the 'map' version is much clearer in intent): totals = splat(get('total'))(orders);
totals = orders.map (o)->o.totalMeh.
totals = map (! "total") orders splat('.total')
But you didn't hear it from me.Do you mean you wanted to emphasize HOFs?
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"?
[1] https://github.com/raganwald/homoiconic/blob/master/2013/01/...
totals = map (.total), orders
http://livescript.net/ var totals = orders.map(function(o){return o.total;});
far easier to understand than var totals = splat(get('total'))(orders);
And, I would had written var totals = orders.map(function(o){
return o.total
})
(note the lack of semicolons and indentation)cheers!
I think the key to a team being explicit vs clever together is that everyone has about the same understanding of explicit vs clever.
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...
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.
/scala guy, very worried by the introduction of macros, despite (perhaps even because of) all the cool things they can do
[1] http://stackoverflow.com/questions/6201657/what-does-splats-mean-in-the-coffeescript-tutorial
[2] http://endofline.wordpress.com/2011/01/21/the-strange-ruby-splat/
[3] http://stackoverflow.com/questions/5917522/unzipping-and-the-operatorAs 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.
makeMapper(makeGetter('total'))(orders)
is much more readable for me than splat(get('total'))('orders')In working with functional code, I prefer naming high order functions with an actor connotation.
function get (object, propertyName) {
return object[propertyName];
};
So now, what is: rcurry(get)? 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.
var splat = applyFirst(applyLast, map);
understandable?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?
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.Let's suppose map is defined as:
> let map (aList) (aFunction) = ...something here...
(this is according to https://github.com/raganwald/allong.es/blob/master/README.md which seems to have the parameters reversed when compared to F#'s Seq.map, Lis.map, etc.)
And let's suppose applyLast is defined as:
> let applyLast (aFunction) (anArgument) = (fun (x) -> aFunction (x) (anArgument))
So makeMapper is:
> let makeMapper = applyLast (map)
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:
> def makeMapper(aGetter: Any => Any): List[Any] => List[Any] = { _.map(aGetter) }
(I guess Haskell has some nice syntax for this too, but I don't think F# has anything like this)
All of this was not easy to get right, I hope I didn't make any mistake regarding the position and number of parameters.
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.
Unfortunately I had trouble articulating this distinction. Perhaps I still do. Here, take a look: http://akkartik.name/blog/libraries.
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?
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.
function mapWith (fn) {
return function (list) {
return map(list, function (something) {
return fn(something)
});
};
};
could be simplified to: function mapWith (fn) {
return function (list) {
return map(list, fn);
};
}; var totals = orders.map(get("total"));
Why needlessly use specialized jargon when common terms work just fine? 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.
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.
I find explicit "do this to that element, then do the other, then put it here" style very hard to read and change.
flatMap fn xs = foldr (++) [] (map fn xs)
or a map of the earth or a map in Minecraft.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.
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.
orders.collect(&:total)
Isn't this all a bit of a storm in a teacup? It seems to me that we should minimise the amount of code because #(code) ~= #(bugs).Care to elaborate?