
Explicit vs. Clever - raganwald
http://raganwald.com/2013/04/02/explicit-versus-clever.html
======
ajross
And yet:

    
    
        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.

~~~
tome
One of the nice things about Python is that it has syntactic sugar for this. I
find

    
    
        [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

~~~
adambard
Are we having a code example party? I like Clojure:

    
    
       (map :total orders)

~~~
bcoates
I'm partial to

    
    
      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.

~~~
adambard

        (defmacro select [total _ coll]
          (let [total (keyword total)]
            `(map ~total ~coll)))
    
        (select total from orders)
    

Problem solved!

~~~
draegtun
Nice.

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.

------
sufian_rhazi
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.

~~~
gbog
> Code needs to read like good copywriting

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.

------
ricardobeat
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.

~~~
raganwald
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...

Clever.

------
akavi
I feel like more than anything, this example demonstrates the value of
lightweight lambdas.

    
    
        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.total

~~~
raganwald
I was deliberately avoiding the map method to keep everything functional.

~~~
akavi
How is map not functional?

Do you mean you wanted to emphasize HOFs?

~~~
raganwald
Yes. The map _method_ is not functional in the sense that the map function is
functional. In actual code, I'd use the method too.

~~~
akavi
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"?

[1]
[https://github.com/raganwald/homoiconic/blob/master/2013/01/...](https://github.com/raganwald/homoiconic/blob/master/2013/01/madness.md)

~~~
chas
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.

~~~
tome
Exactly. There's no point being "functional" for the sake of functions. It's
for the benefits this style brings, an important one being composability.

------
ante_annum
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.

------
jiaaro
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...

------
rickmode
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.

~~~
lmm
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

~~~
rickmode
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.

~~~
lmm
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.

~~~
rickmode
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.

------
scotty79
I think that lots of confusion is brought by strangeness of jargon names

    
    
      makeMapper(makeGetter('total'))(orders) 
    

is much more readable for me than

    
    
      splat(get('total'))('orders')

------
klochner
It's a bit confusing that splat is being used in an atypical manner - I've
usually seen it used to designate an argument aggregator.

    
    
         [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-operator

~~~
raganwald
Based on feedback, expect a major naming revision to the allong.es library
soon.

------
leke
Is is just me, or has JavaScript got really fucking weird lately?

~~~
raganwald
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.

------
ww520
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.

~~~
raganwald
That's really interesting. It's especially interesting if you think of this:

    
    
        function get (object, propertyName) {
          return object[propertyName];
        };
    

So now, what is: rcurry(get)?

~~~
ww520
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.

------
scotty79
Do you know any notation that makes

    
    
      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?

~~~
anonymous
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.

------
akkartik
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.

Unfortunately I had trouble articulating this distinction. Perhaps I still do.
Here, take a look: <http://akkartik.name/blog/libraries>.

~~~
sanderjd
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?

~~~
akkartik
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.

------
ScottBurson
You have an unnecessary lambda^W anonymous function expression in 'mapWith':

    
    
      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);
        };
      };

~~~
raganwald
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.

------
AnIrishDuck
If I'm understanding get() correctly here, why not just use:

    
    
        var totals = orders.map(get("total"));
    

Why needlessly use specialized jargon when common terms work just fine?

~~~
raganwald
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.

------
spullara
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.

~~~
tome
I happen to agree with you, but I have probably interpreted your comment in
exactly the opposite way to which it was intended!

I find explicit "do this to that element, then do the other, then put it here"
style _very_ hard to read and change.

~~~
spullara
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.

------
walrus
How pointless! ;)

~~~
raganwald
BEST COMMENT EVAR.

------
tunesmith
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.

------
anonymoushn
splat does not seem to be a function that produces a flatMap. In the contexts
google is familiar with, a flatMap is either something like this

    
    
      flatMap fn xs = foldr (++) [] (map fn xs)
    

or a map of the earth or a map in Minecraft.

~~~
raganwald
That was a typo, I started out writing about "soak," which is to flatMap as
"splat" is to map. Thanks.

------
sly010
Every time I have to choose between "clever" and "explicit", I just choose
"consistent".

------
worldsayshi
> 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.

------
andreyf
> 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.

------
zzzcpan
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.

------
ioquatix
How about:

    
    
        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).

------
carlosantelo
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.

------
tjholowaychuk
With names like pluck and splat... enough said haha

~~~
qu4z-2
I'm afraid you did not, in fact, say enough.

Care to elaborate?

