What we're interested in is simplicity. And this is an objective measure.
This (A):
partial(Math.max, 2)
is objectively simpler than this (B): function atLeastTwo(someNumber) {
return Math.max(2, someNumber);
}
Why is it objective? Because it expresses the same exact value (i.e., function) with an objectively fewer number of concepts.When (A) is unfamiliar it feels less intuitive, but with practice/experience that sense of unintuitive goes away, and then you are left with (B) a simpler expression -- and therefore less cognitive load on the developer trying to make progress.
B gives you all the information you need to know what's going on.
A requires you to go dig up what "partial" is doing (which, it turns out is itself doing some wacky gymnastics to turn one of its arguments into a function and the other into a parameter (and the parameter of whatever gets passed into the returned function as the other parameter of the function that was originally passed in).
Observe how much writing it took just to describe what's going on with your "A". No explanation at all is required for B. It's complete as written. Simple, and intuitive all in one.
It takes a lot to describe what's going on with A because it's a relatively new concept, related to the subject of the article. If partial application would be just as well known as function definition, the explanation could be shorter.
Only then do you get to learn those unnecessary gymnastics, so you can follow the rest of his code.
Fundamentally this is what programming is all about - finding the commonality that you can pull out and reuse, rather than doing every single case by hand.
The whole article makes sense as telling something new to the one who doesn't know what's the point-free style is - but might know what the partial application is. It's a feature, not a bug when something is learned as the result of reading the article and can be applied later.
There are many, many things that are /simple/ that are actually pretty hard to understand. Most of higher maths, for instance.
your idea of B being more simple is largely driven by the fact that you likely have more experience writing code in that manner. With experience writing functional code you should find that both A and B are clear at a glance.
{ x => Math.pow(2, x) }
Obvious it's a function of 1 argument. The pointfree variant requires 1 extra concept [partial], plus building a mental map of the arguments, to reconstruct what kind of a function [or value!] the pointfree expression represent.Furthermore, implementing the reverse check is syntactically symmetric in the pointed version:
{ x => Math.pow(x, 2) }
Whereas the pointfree version, assuming the function is not commutative, accumulates even more concepts: partial(flip(Math.pow), 2) f1 = pow 2
f2 = pow _ 2
This makes it look like a lambda but without having to bind an argument explicitly. Makes it much nicer to write complicated expressions (although I can't speak to the readability for people less fluent in the syntax). f1 x = pow 2 x
f2 x = pow x 2A is maybe more _elegant_, but IMHO it has a higher mental load.
partial(Math.max, 2)
> is objectively simpler than this (B): function atLeastTwo(someNumber) {
return Math.max(2, someNumber);
}
What the article failed to convey to me was why either of these are better than just using Math.max(2, <expression>)
everywhere I need to use the greater of 2 and some expression. It didn't really explain why we want to hide the 2 at the places where we need to use 2 as a lower bound on a value.Back to atLeastTwo. It's a trivial example, but consider if you actually did have Math.max(2, e) throughout your code base. What is the meaning of this expression? Gives you either 2 or e whichever is greater. Great, we understand what it is. But now, why? What is magic about 2. Giving it a name can convey the why (though atLeastTwo does not). Giving it a name also makes it less error-prone. Suppose you fat-fingered 22 into a couple spots, how do you (quickly and easily) identify that error? If it's just Math.max(22, <expression>) you can't even know whether it's wrong, just that it has a 22 in it instead of a 2.
Sufficient testing would (hopefully) reveal that you used 22 instead of 2, but it may lead to subtle bugs that are hard to identify. We have this issue in a codebase at work where we do a lot of bit twiddling. Instead of setXFlag(&data_word, val) we have something like setBits(7,1,&data_word,val). That 7,1 determines which bit to start at (7) and how many (1) to put val into. Great, it works. But when the data format got changed we had to change every single reference to 7,1 to 6,1. Unfortunately it was really, really fucking hard to find all those references and correctly discern which ones needed to be changed and which ones didn't.
I decided to let that ride rather than change it to something like MIN_SAFE_VALUE like it should be in real code.
So supposed that is fixed, and we have MIN_SAFE_VALUE defined somewhere, so that we don't have to worry about typos (much...if we have a MIN_SAVE_VALUE too that could be trouble).
Also suppose that MIN_SAFE_VALUE is used instead of 2 in "partial(Math.max, ...)". Alternatively, if that is the only place that actually uses MIN_SAFE_VALUE, with everyone else using it via the partial function, then I suppose it would be OK to use the 2 literally there, and name the result of partial with a name that says it it ensures the minimum safe value.
Same for the "function atLeastTwo" approach. Give it a good name, and get rid of the magic number in the definition, unless that is the only place that uses MIN_SAFE_VALUE directly and then maybe it is OK for that to be where the literal 2 appears and not have a MIN_SAFE_VALUE constant defined. (I'd still go for a defined constant, because it and any other such magic numbers can be defined in one place making it easier for people who need to tune things later).
Now I can see why the "partial" approach and the regular function approach might win over inline "Math.max(MIN_SAFE_VALUE, ...)", because they both make it easier to change how the safety constraint is enforced.
For example let's say this code is part of the control system for a power plant. The engineers revise the rules to allow going 10% under the normal minimum safe value when the plant is operating more than 20% below peak capacity.
With the regular function approach we only have to change that one function so that it checks the operating level and adjusts the threshold it checks against accordingly. With the partial approach we could probably also do something like that (although maybe we'd need a helper function since we would no longer be applying partial to an already existing function.
With the "Math.max(MIN_SAFE_VALUE, ...)" everywhere approach we'd have to change all those places. (Except if we were in C or C++, and MIN_SAFE_VALUE was a simple #define. Then maybe we could change that from "#define MIN_SAFE_VALUE 2" to "#define MIN_SAFE_VALUE current_safe_value()" and write a current_safe_value() function. That still limits is to only handling safe value rules that involve a >= comparison of the value with a threshold, so is still not as flexible as the normal function approach.
But note that this advantage comes from pushing the algorithm of the safety check down to underlying functions, instead of exposing it at the point of use. Using Math.max at the place where you are actually checking the value and using the corrected value exposes at that point that the algorithm is to compare to something and pick the larger of the two.
For me the article just didn't explain why pushing the algorithms lower in point-free style is better than doing that in normal function style, and didn't seem to have any other compelling thing point-free does. This is probably a topic that needs more than the 148 words in the article's "Why Does It Matter?" section.
Some of the comments here using Haskell look more interesting, but I've only looked a little at Haskell. They all quickly hit my limit--the before and after Haskell examples look like something compelling is happening but I just am not yet able to see what that is.
Even if you're only using it in one place, `atLeastTwo(some_value)` is simpler than `partial(Math.max, 2)(some_value)` in at least that way.
What we are really interested in is whether it makes programs easier to understand, less easy to misunderstand, and less likely to contain errors. A measure that is more objective but doesn't address these issues is not intrinsically better.
y = (x) => {return Math.max(2, x)}
To me that just looks nice, and it's clear how Math.max is being used. With the partial (A) I have to have some implicit knowledge of the function, where as in (B) or my version I can see how it's being used. Perhaps I'm a bit dis-functional. y = => {Math.max , 2}
that would be clearer still (setting aside familiarity with the language). Whether "=>" or "partial" is a better name is a separate question.It does not express anything with objectively fewer concepts. Instead it distributes the concepts differently.