6

In case it matters this is about functional programming in JavaScript and in my examples I’ll be using Ramda.

While everybody at work has fully embraced functional programming, there’s also a lot of discussions around how to do it “right”.

These two functions will do exactly the same thing: take a list and return a new list in which all strings have been trimmed.

// data-centric style
const trimList = list => R.map(R.trim, list);
// point-free style
const trimList = R.map(R.trim);

So far so good. However with a more complex example, the difference between the two styles is striking: take a list and return a new list in which all strings are equal to a property found in an object.

var opts = {a: 'foo', b: 'bar', c: 'baz'}; 
var list = ['foo', 'foo', 'bar', 'foo', 'baz', 'bar'];

myFilter(opts, 'a', list); //=> ["foo", "foo", "foo"]
myFilter(opts, 'b', list); //=> ["bar", "bar"]
// data-centric style
const myFilter = (opts, key, list) => {
  var predicate = R.equals(opts[key]);
  return R.filter(predicate, list);
};
// point-free style
const myFilter = R.converge(
  R.filter, [
    R.converge(
      R.compose(R.equals, R.prop), [
        R.nthArg(1),
        R.nthArg(0)]),
    R.nthArg(2)]);

Besides readability and personal taste, are there any reliable evidences to suggest that one style is better suited than the other in some circumstances?

customcommander
  • 17,580
  • 5
  • 58
  • 84
  • 2
    This is an opinion based question, which aren't allowed by the rules. That said, there's no controversy about this. There's no point in forcing a point free style. Usually people will eta-reduce, i.e. turn `λx. f x` into `f`, if there is no problem with it (the semantics aren't the same in call-by-value when `f` is undefined). But not much else... – Jorge Adriano Branco Aires Dec 07 '18 at 13:41
  • I don't think point-free is a *requirement* of functional programming. It comes as a byproduct of it and in many cases it could be good as it reduces the complexity of definitions and as consequence their understanding. But not always - excessive point-free programming can lead to needlessly generalised functions and as a result, they could be very hard to effectively understand. As a simple example, if you try to reduce a list and pass a function that uses `(a, b) => a+b` as one of its building blocks, it may be hard to know the result if you have a mixed content, e.g, `[1, "2"]`. – VLAZ Dec 07 '18 at 13:58
  • The rules make provision for "Constructive subjective questions" which I think mine falls under. It's a genuine question and I'm looking for evidences not opinions. I think my question is valid but I am happy to be proven wrong. – customcommander Dec 07 '18 at 14:06
  • 1
    @customcommander I think your question is interesting and important...but not on-topic here. – Jared Smith Dec 07 '18 at 14:13
  • @JaredSmith I have reworded my question. – customcommander Dec 07 '18 at 14:27
  • 1
    I agree with the closing of this question, although I like it too, and would like to give my answer, which is too long for a comment, and mostly an expansion on the comment from @JorgeAdriano. I think it might not be closed if you were to try again with a focus more like, "when is it appropriate to choose point-free style vs a data-centric style?" While that's still opinion-based, I believe it is covered by the "constructive" exception. In the end, the focus should be on the readability of the code. – Scott Sauyet Dec 07 '18 at 14:45
  • Thanks @ScottSauyet I have reworded my question as you suggested and removed a paragraph that could be seen as subjective. It just needs more votes to reopen the question. – customcommander Dec 07 '18 at 15:03
  • @customcommander VTR'd. – Jared Smith Dec 07 '18 at 15:13
  • VTR's too -- and a handy abbreviation. – Scott Sauyet Dec 07 '18 at 16:57
  • I definitely find this question useful and interesting; I didn't actually know the term "point-free". However, even with the update, I think this question is still not a good fit for the site. The answer would still be stylistically subjective. Can you qualify "better"? Are you looking for answers based on performance or maybe readability? – zero298 Dec 07 '18 at 18:18
  • @zero298 Thanks for your feedback. I appreciate that you find this useful and I'm keen to reopen this question. I have had another look at the question and I believe that the question is now genuinely constructive. Do you think different? – customcommander Dec 07 '18 at 20:19
  • @vlaz Do you have any feedback on the current state of the question (I have edited it several times now). Do you still think it is subjective? – customcommander Dec 08 '18 at 20:09

3 Answers3

8

I know of no evidence that demonstrates advantages of one style over the other. But there is a clear trend in the history of programming towards higher abstractions... and an equally clear history of resistance to this trend. A move from Assembly to Fortran or LISP was a move up the abstraction stack. The use of SQL rather than bespoke B-tree retrievals was a another. The move to FP, both within a language like Javascript and in the changing landscape of programming languages, is to my mind a similar move.

But much of that has to do with elements more fundamental than this syntactic decision: equational reasoning means that we can build our own abstractions on top of more solid footing. So purity and immutability are essential; point-free is merely nice to have.

That said, it is often simpler. And that is important. Simpler code is easier to read, easier to modify. Note that I distinguish between simple and easy -- the distinction articulated in the classic talk by Rich Hickey. Those new to the style will often find it more confusing; so too the assembly programmers who abhorred the next generation of language and all their ilk.

By not defining intermediate variables, by not even specifying arguments that can be inferred, we can significantly improve simplicity.

It's hard argue that this:

const foo = (arg) => {
  const qux = baz(arg)
  return bar(qux)
}

or even this:

const foo = (arg) => bar(baz(arg))

is simpler than this:

const foo = compose(bar, baz)

And that's because while all three involve these notions:

  • function declaration
  • function reference

the second one adds also:

  • argument definition
  • function body
  • function application
  • nesting of function calls

and the first version has:

  • argument definition
  • function body
  • function application
  • local variable definition
  • local variable assignment
  • the return statement

while the third one adds only

  • function composition

If simpler means having fewer notions entwined, the point-free version is simpler, even if it's less familiar to some people.


In the end, much of this comes down to readability. You spend more time reading your own code more than you do writing it. Anyone else spends a lot more time reading it. If you write code that it is simple and readable, you've made the experience much better for everyone. So where point-free code is more readable, use it.

But don't find it necessary to remove every point in order to, ahem, prove a point. It's easy to fall into the trap of trying to make everything point-free just because you can. We already know that it's possible; we don't need to see the gory details.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • The difficulty with *keep it simple* is its relativity. It means something completely different for a seasoned developer than it does for a rookie. –  Dec 12 '18 at 21:10
  • 1
    @reify: The point I was making -- and the thrust of that Rich Hickey talk -- is that *ease* / *familiarity* is actually the relative measure; *simplicity* is in fact more objective. – Scott Sauyet Dec 18 '18 at 13:47
6

The academic term is eta conversion. When you have a function with redundant lambda abstraction like

const trim = s => s.trim();
const map = f => xs => xs.map(x => f(x));

const trimList = xs => map(trim) (xs); // lambda redundancy

you can simply strip the last lamdba abstraction by eta reduction:

const trimList = map(trim);

When you use eta reduction extensively, you end up with point free style. However, both versions are perfectly fine in the functional paradigm. It's just a matter of style.

Actually, there are at least two reasons to use eta abstraction (the opposite of eta reduction) in Javascript:

  • to fix Javascript's multi argument functions like I did with map = f => xs => xs.map(x => f(x))
  • to prevent immediate evaluation of expressions/statements (lazy evaluation effect) as in recur = f => x => f(recur(f)) (x)
  • When you say `there are at least two reasons to use eta abstraction`, did you mean `eta conversion`? – customcommander Dec 12 '18 at 20:26
  • 1
    @customcommander *eta abstraction* (adding functions) and *eta reduction* (removing functions) are subsumed under the general term *eta conversion*. –  Dec 12 '18 at 21:02
1

there are some good answers and in my opinion, mixing the two styles is the way to go.

the last point-free style example is a little bit confusing, you can make it less confusing :

const myFilter = converge(
  filter,
  [compose(equals , flip(prop)) , nthArg(2)]
 )
SC K
  • 141
  • 8