10

I am having a hard time understanding a bit of example code from the book JavaScript Allongé (free in the online version).

The example code is a function for calculating the circumference for a given diameter. It shows different ways to bind values with names. One way to go about it, according to the book is this:

(
 (diameter) =>
  ((PI) => diameter * PI)(3.14159265)
)(2);
// calculates circumference given diameter 2

It further states:

Well, the wrinkle with this is that typically, invoking functions is considerably more expensive than evaluating expressions. Every time we invoke the outer function, we’ll invoke the inner function. We could get around this by writing

(
 ((PI) =>
   (diameter) => diameter * PI
 )(3.14159265)
)(2);

I cannot understand how it gets around the situation with calling two functions, aren't there exactly two function calls in both the examples? How do they differ from each other?

3
  • 1
    I might be mistaken, but as far as I can tell, they only switched the order of the functions, and aren't actually saving anything in terms of performance. Commented Aug 7, 2016 at 11:59
  • 1
    It's somewhat misleading to say that one is "considerably more expensive" than the other. Apart from a very small set of exceptions it makes no practical difference at all. Commented Aug 7, 2016 at 12:01
  • 3
    I wouldn't trust a book whose author seems unaware of the existence of Math.PI... Commented Aug 7, 2016 at 17:30

4 Answers 4

6

This probably looks a bit confusing because I don't think it's explained very well. Or, rather, I don't think it's explained in a typical JavaScript way.

Let's break down the examples

First Example

Breakdown

var calculateCircumference = (diameter) => (
    (PI) => diameter * PI)(3.14159265)
);

calculateCircumference(2); // 6.2831853

Arranged like this, here is what happens if you call this code

  1. You pass the diameter (e.g., 2)
  2. A new function is created that takes PI as parameter and uses it to calculate the circumference. This function is immediately invoked
  3. The function uses both variables present to do the calculation

Aside from being wasteful computation-wise (two invocation) this example is also convoluted for no good reason. The inner function is pointless and doesn't gain you anything. It's probably where the example loses lots of its clarity - seems like the only reason to have the example work as it is, is to set up for the second example.

Second Example

On currying

Before tackling the example, it seems like the book probably failed to mention how exactly it works. The second example leverages a technique called curry which is used in functional programming - it is not specific to JavaScript but it is still widely known as that name in the JavaScript world. A very brief overview of currying

//non-curried
function add(a, b) { // or, in ES6: (a, b) => a + b;
    return a + b;
}

//curried
function curryAdd(a) { //or in ES6: (a) => (b) => a + b;
    return function(b) {
        return a + b;
    }
}

//invocation
add(2, 3); // 5
curryAdd(2)(3); // 5

I will not go into detail but essentially, a curried function that takes multiple parameters, can be passed less and it will return a new function that can take the rest. When all the parameters are satisfied, you will get the result - in a formal notation, the curryAdd function will be expressed as curryAdd :: Number -> Number -> Number - it's a function that takes a number and returns another function that takes a number which finally returns another number. For why you would want to do that, here is an example - it's trivial but it gets the point accross:

//add5:: Number -> Number
add5 = curryAdd(5);

add5(3); // 8
add5(10); // 15
[1, 2, 3].map(add5); // [6, 7, 8]

Currying is a bit like partial allocation of functions but the two are not (necessarily) the same thing.

Breakdown

With that said, let's look at the second example:

//curryMultiply :: Float -> Float -> Float
(PI) => (diameter) => diameter * PI
//another way to write it:
//(a) => (b) => a * b

Hopefully that clarifies what is going on a bit. I'll re-write the rest of the example into what is actually happening:

// calculateCircumference :: Float -> Float
var calculateCircumference = curryMultiply(3.14159265);

calculateCircumference(2); //6.2831853

The second example's code is equivalent to the above. It avoids invoking a function twice because the outer function (which I dubbed curryMultiply) is invoked only once - any time you call the calculateCircumference function, you are only evaluating the inner function.

Sign up to request clarification or add additional context in comments.

3 Comments

I don't think the book is trying to do currying there, but rather explain name bindings in the Scheme way. Otherwise, +1
@Bergi I also don't think it's trying to do currying but it's what is effectively happening. I think that understanding currying would lead to understanding the code, since all it's doing is evaluating a chain of functions that get returned from other functions.
@vid Very well explained thank you very much. You guys are right this example is a setup to present the case for using const, it proceeds to use const for the PI value in the later examples.
3

I believe the emphasis is on the phrase "Every time we invoke the outer function…", which is indeed confusing as the outer function is only invoked once in the example (as an IEFE). One should be able to grasp the difference better with this example:

const circumference = (diameter) => 
  ((PI) =>
    diameter * PI
  )(3.14159265);
console.log(circumference(2));
console.log(circumference(5));

const circumference = ((PI) =>
  (diameter) =>
    diameter * PI
)(3.14159265);
console.log(circumference(2));
console.log(circumference(5));

But apparently the author does not want to introduce variable declarations here, so maybe it would be written

((circumference) => {
  console.log(circumference(2));
  console.log(circumference(5));
})(((PI) =>
  (diameter) =>
    diameter * PI
)(3.14159265));

to the same effect :-)

1 Comment

Brilliant answer! It took me a while to get this but I understood what's going on finally. Thank you!
3

You should have a look at immediately-invoked function expression (IIFE); that's a design pattern...

Basically: You declare a function and invoke it immediately... this was sometimes used as an expedient for creating a lexical scope, just in order to avoid global variables...

// The way we're confident...
function logFoo() { console.log(1, 'FOO'); }
logFoo();

// Using and IIFE
(function() { console.log(2, 'FOO'); }());
// OR for better readability
(function() { console.log(2, 'FOO'); })();

As you can see, we use parenthesis for wrapping/executing an expression (...) and parenthesis as a function call operator. That mean: evaluate that expression and call what it return.

Of course, because we're using functions, we can pass them arguments:

function log(what) { console.log(3, what); }
log('Foo');

// IIFE
(function(what) { console.log(4, what); })('Foo');

The last thing you probably already know is the Arrow Function, introduced by ECMAScript 6:

(what => console.log(what))('Foo');

Finally, you're fighting with a nested round trip of IIFE(s).

Comments

2

What the book may suggest is that the JavaScript compiler is more likely to inline the PI function in the second method. But this would only make sense if we call these methods several times with different dynamic diameters. Otherwise, the compiler is likely to inline the diameter function just as well.

At the end of the day, what really matters from a performance perspective is what the JavaScript engine is really doing with these functions anyway.

Below is a test that suggests that there's almost no difference at all between the two methods. At least on my box.

You may want to execute more iterations, but please note that this is apparently extremely slow on Edge.

// This is a warmup to make sure that both methods are passed through
// Just In Time (JIT) compilation, for browsers doing it that way.
test1(1E5);
test2(1E5);

// Perform actual test
console.log('Method #1: ' + test1(1E6).toFixed(2) + 'ms');
console.log('Method #2: ' + test2(1E6).toFixed(2) + 'ms');

function test1(iter) {
  var res, n, ts = performance.now();

  for(n = 0; n < iter; n++) {
    res = (
      (diameter) => ((PI) => diameter * PI)(3.14159265)
    )(Math.random() * 10);
  }
  return performance.now() - ts;
}

function test2(iter) {
  var res, n, ts = performance.now();

  for(n = 0; n < iter; n++) {
    res = (
      ((PI) => (diameter) => diameter * PI)(3.14159265)
    )(Math.random() * 10);
  }
  return performance.now() - ts;
}

1 Comment

Thank you very nice learnt something new today about Inline expansion

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.