A common thing you need to do is to take a value, do something with it by passing it to a function, and then do some more with the return value, in a chain. Whenever I run into this type of scenario, I get unsure about how best to write the code.
As an example, let’s say I have a number, num
, and need to take the floored square root of it and turn it into a string. Here are a few possibilities (in JavaScript).
I might simply pass num
to one function at a time, and replace num
with the return value:
function floorSqrt(num) {
num = Math.sqrt(num);
num = Math.floor(num);
num = String(num);
return num;
}
This is nice since I don’t need the original num
any more, but in practice it might be confusing to start with one thing (a number) and end up with something completely different (a string).
I can save each step in a new variable:
function floorSqrt(num) {
var sqrtNum = Math.sqrt(num);
var floorSqrt = Math.floor(sqrtNum);
var stringNum = String(floorSqrt);
return stringNum;
}
Although it seems wasteful to declare all those variables and come up with good names for them.
Or I can just do it all as a one-liner:
function floorSqrt(num) {
return String(Math.floor(Math.sqrt(num)));
}
But for more than two functions that approach gets incredibly unreadable, almost like code golf.
It seems to me like the most beautiful way to do this would be with some stack-based language, which might look something like this (pseudo code):
[sqrt floor toString]
Is there a way to do something similar with JavaScript? List some functions, run one at a time, and use the return value of each one as the argument for the next? The closest is how some libraries like jQuery or Underscore.js allow you to chain methods, something like this:
function floorSqrt(num) {
return _.chain(num).sqrt().floor().toString().value();
}
I’m sure many wise people have thought wise things about this. What are some thoughts on pros and cons of the different styles?
2
TL;DR: The Reduce/Compose Functional Programming Idiom
# define/import: compose(fn1, f2)(arg) => fn2(fn1(arg))
# define/import: reduce(fn, [l1, l2, l3, ...]) => fn(fn(l1, l2), l3)...
commands = [Math.sqrt, Math.floor, String]
floorSqrt = reduce(compose, commands)
# floorSqrt(2) == String(Math.floor(Math.sqrt(2)))
Notes:
compose
from Eloquent Javascriptreduce
requires a 2 argument definition
Using reduce
and compose
is a very common idiom used when you want to setup a pipeline of transformations for data to go through. It’s both readable and elegant.
<rant>
There are too many programmers who conflate readability with familiarity and vice versa. They believe an unfamiliar idiom is unreadable, like the reduce/compose idiom only because it is unfamiliar. To me this behavior is more aligned with a diletante, not someone who is serious about their craft. </rant>
4
Our goal as software engineers, should be writing code that is clear and understandable for other humans, and I’m glad you’ve already realized that writing too many nested function calls you end up with code that is hard to read.
So assuming your language doesn’t support jQuery-like syntax you mentioned at the end, seems the only good alternative you have left is to break up complex chain of function calls into simpler statements where each assigns a temporary value to an intermediary variable. So now you are left with only two choices: 1) reuse same variable name for each step and 2) define a new local variable for each step.
And assuming you’ve arrived at the same conclusion as me, I’ll help you make the final choice: Use new variable names for each step
The argument “it is wasteful to declare 5 variables when I can use one” is a bit of premature optimization. I’ve had embedded guys join my team and this was one of their habit that I would always have to break. Each variable should have a) meaningful name and b) always the same meaning. Then when one reads through the code he never has to guess what “num” means especially since code tends to not stay as simple as your example. Imagine someone else comes long, adds a few loops and if-statements and keeps reusing your “num” variable. Now to understand what’s in it, you would have to trace back and perform mental gymnastics in trying to determine just which statement was hit and how many times was “num” reassigned.
Assuming you are not doing embedded programming on a some board with 8 KB of RAM, there’s no reason to reuse the same variable multiple times for multiple purposes. Come up with good, clear names and use them. When the stack unrolls, it costs exactly same amount to remove 1 int from stack as it takes to remove 6 ints.
9
If you are not restricted to Javascript, many functional programming languages allow you to use an approach similar to that suggested by dietbuddha (function composition operator). I will illustrate this using Haskell.
Your solution with intermediate variables would go like this:
floorSqrt :: Float -> String
floorSqrt num = let
sqrtNum = sqrt num
floorSqrt = floor sqrtNum
stringNum = show floorSqrt
in
stringNum
The one-liner would be
floorSqrt num = show (floor (sqrt num))
or also
floorSqrt num = (show . floor . sqrt) num
or
floorSqrt = show . floor . sqrt
i.e. you can compose the three functions using the .
operator and apply the resulting function to the argument num
. By using a composite function you can leave out the argument num
from the definition, as illustrated in the last example above.
In general, if you have a sequence of functions
f1, ..., fn
where, for i ∈ {1, …, n} : fi : ai -> bi, and for i ∈ {1, …, n – 1} : bi = ai + 1, you can define the composite function
compositeFunction = fn . . f1
If all the functions you want to compose have the same type a -> a
, you can apply the foldr
function to the list of functions you want to compose. For example, imagine you have the function
floorSqrt :: Float -> Float
floorSqrt = (fromIntegral . floor) . sqrt
(note that we have to insert fromIntegral
because floor
does not return a Float
). This can be written as
floorSqrt = foldr (.) id [fromIntegral . floor, sqrt]
where foldr
corresponds to reduce
in dietbuddha’s answer, and .
to compose
. The id
function (identity) is needed as a starting point for the folding / reduction (I suppose this is implicit in the JavaScript example).
You can use the above pattern for any sequence of functions f1, ..., fn
where, for i ∈ {1, …, n}, fi : a -> a:
compositeFunction = foldr (.) id [fn, ..., f1]
In Haskell, you cannot use this last pattern for your original example because foldr
requires all functions in the list to have the same type. You can use it in JavaScript because it does not have this restriction (see dietbuddha’s answer).
I would not nest too many methods when using the standard math and string methods, for example, in Javascript. But there are libraries which have a so-called fluent interface, these kind of API’s are especially made for making readable code by method chaining. You will find tons of articles when you google for those keywords.
Languages such as you describe exist, the most popular (popular being a relative term) is FORTH
Your example would look something like this in FORTH:
: floorSQRT ( NUM -- NUM )
Sqrt
Floor
ToString ;
(Though FORTH is extremely low level, and so doesn’t really have a string type the way we think of it now.)
If you really wanted to do something like this in JavaScript, you could, by creating your own versions of these methods that took an array as the first parameter, something like:
function Sqrt(stack) { stack.push(Math.sqrt(stack.pop())) };
function Floor(stack) { stack.push(Math.floor(stack.pop())) };
function MakeString(stack) { stack.push(String(stack.pop())) };
function floorSqrt(stack) {
Sqrt(stack);
Math.floor(stack);
MakeString(stack);
}
stack.push(num);
floorSqrt(stack);
num = stack.pop();
This would work, but it would be massively confusing to most JavaScript programmers, so don’t ever do it.
Having worked with FORTH back in the day, the idea of a stack based language is very appealing, but in practice, for any reasonable sized piece of code, it quickly becomes confusing. Because everything is implicit, it’s hard to remember what each method is going to actually operate on. Much more confusing than your original code, actually. Honestly, if I were writing this code, and wanted it to be clear, it’d look like this:
function floorSqrt(num) {
num = Math.sqrt(num);
num = Math.floor(num);
return String(num);
}
I don’t really find that confusing at all. It’s very clear.
If you really want to do chaining, you could try to make it clearer like this:
function floorSqrt(num) {
return String(
Math.floor(
Math.sqrt(num)
));
}
But again, I don’t think that’s really necessary. I think the original is perfectly fine and straightforward.
As Doc Brown says, this is a fluent interface, and is easily achievable in javascript, to use your example:
Number.prototype.sqrt=function(){return Math.sqrt(this);};
Number.prototype.floor=function(){return Math.floor(this);};
String.prototype.alert=function(){alert(this);return this;}
(5).sqrt().floor().toString().alert(); //alerts "2"
1