7

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?

Peter Mortensen
  • 1,050
  • 2
  • 12
  • 14
last-child
  • 189
  • 1
  • 6
  • 3
    Design patterns?!? This is just a method. Your example is a bit weird; you would usually separate string formatting from mathematical operations. In your final example, many languages support extensions and you can easily write the methods to simulate member functions. – Dave Hillier Sep 02 '13 at 19:11
  • 1
    Yeah, the example probably isn't great, but you can imagine any similar sequence of function calls. Design pattern might not be a good word for this... I'll change it to "coding style", maybe? Thanks. – last-child Sep 02 '13 at 20:34

6 Answers6

17

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:

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>

dietbuddha
  • 8,677
  • 24
  • 36
  • I like the philosophy but I don't understand the code. Is there something missing, or am I just thinking in the wrong language? – david.pfx Apr 17 '14 at 06:42
  • 1
    @david.pfx `reduce` is a very general way of using a two-argument function to combine the elements of an array; it crops up all over the place: `sum = reduce(plus, myNums)`, `product = reduce(times, myNums)`, `join = reduce(concat, myStrings);`, `all = reduce(and, myBools)`, `any = reduce(or, myBools)`, `biggest = reduce(max, myNums)`, `smallest = reduce(min, myNums)`, `run = reduce(then, myCommands)`, and so on. In this case, `compose` "chains" two function calls (a more precise term is it *composes* them, hence the name!), so `chain = reduce(compose, myFuncs)` creates a big `chain` function – Warbo Sep 12 '14 at 15:50
  • 2
    @Warbo good examples, but note that `reduce` is not always the best way. For example, `biggest` is O(n) using reduce but could be O(lg n) by divide and conquer. For `all`, you could stop as soon as you get to `false`, and similarly for `and`. Beware of [Shlemiel the painter](http://en.wikipedia.org/wiki/Joel_Spolsky#Schlemiel_the_Painter.27s_algorithm) in your `join` implementation (if not with `\0`, maybe with excessive allocation). In general, though, `reduce` is indeed a very useful tool. – wchargin Sep 12 '14 at 21:35
  • @warbo: I understand reduce perfectly and use it routinely in other languages. Compose is much less widely used. What is missing from the answer is the link between the code you actually wrote and the underlying concepts and implementation details that make it work. It's not an actionable answer as it stands. – david.pfx Sep 12 '14 at 23:20
9

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.

DXM
  • 19,932
  • 4
  • 55
  • 85
  • Also, the compiler (with JIT in the case of JavaScript) will likely figure out that it can reuse the same space for all these variables and output the same code anyway. – Gort the Robot Sep 02 '13 at 21:33
  • 4
    I have worked on embedded systems (on and off, over the last 15 years). I have had to deal with some crappy/out dated compilers, but I have never found one that couldn't properly allocate variables to stack and registers. Your embedded programmers had joined a Cargo Cult. – Dave Hillier Sep 02 '13 at 22:02
  • 2
    Writing readable code doesn't mean you write code to the lowest common denominator, or the least knowledgable. You should be able to draw from any supported paradigms/idioms that provide compact, readable code. For example, reduce + compose creates a very readable and elegant solution. – dietbuddha Jan 11 '14 at 05:04
  • 1
    @dietbuddha: "we as developers have responsibility to delivery readable code" - do what makes more sense for you. But also keep in mind people around you. If you are in a functional language, by all means do composition. However if you are using language were functional style isn't popular or looks fuglier than a series of assignments (e.g. I can write "functional" C++ or Java, but I typically wouldn't), for the love of everything that's good, stick with stupid assignments instead of writing something that no other developer on your team has ever seen. – DXM Jan 11 '14 at 05:08
  • 2
    @DMX: Obviously you don't write using idioms from paradigms that are not supported by the language. However, I would also expect that a C++ developer use the STL idioms to write a more elegant solution, rather than avoiding its use because someone may be unfamiliar with it. I want to push people to use the best tool for the job, as provided by the language and not limit based on knowledge. If you don't know something learn it, add it to your toolbox and be a better programmer for it. The argument of readability I find lately seems conflated unfamiliarity of very readable idioms. – dietbuddha Jan 11 '14 at 05:19
  • @dietbuddha: unfortunately "elegant" is a very subjective definition. Now that C++11 is out with lambda's functional stuff doesn't look nearly as bad, but I know of C++ guys who have been writing "elegant" loops using the previous standard, and that always reminded of printf("%8.2f\n", x) vs. cout << someguy << other-guy-I-can't-remember << x << some-new-line-thing; I love C++, I love STL, I love Boost but I am still sticking to my non-elegant printf. – DXM Jan 11 '14 at 05:30
  • If you're performing a chain of operations on the same value, re-naming your data for each sub-step means you're adding complexity without any benefit. Sure, use new names if the meaning changes... but the meaning of `num` doesn't change in sequences. – DougM Jan 11 '14 at 06:13
  • @DougM: although you are starting with a premise that "num" is a good name to begin with. That name has just as much meaning as "#define ONE_MILLION 1000000" which I've seen used as a "fix" for no magic numbers in code. However, I didn't think discussing variable naming was in scope of this post. – DXM Jan 11 '14 at 06:57
  • It was a comment to your own answer; if the OP is only chaining calls on the same variable and not altering its type, there's no reason to change its name for each and every step, as you indicated in your bolded example. – DougM Jan 11 '14 at 07:21
5

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

Giorgio
  • 19,486
  • 16
  • 84
  • 135
4

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.

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
4

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.

Gort the Robot
  • 14,733
  • 4
  • 51
  • 60
2

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"
Tom
  • 251
  • 1
  • 5