An Interesting Task for an Interview. Currying and Partial Application of a Function

JavaScript

I am currently attending some job interviews. Some of them are boring, others are interesting. During one of the interviews they asked me to write a function that could add two numbers. So, I wrote:

it ('should add two numbers', function () {
    var add = function (a,b) {
      return a + b;
    };
    assert.equal(add(2,3), 5);
  });

— «But what if that the function signature should be something like add(num1)(num2)?» — «No problem!»

I think that they want to check whether I know about returning functions from functions. So, I wrote the following:

it ('should be called like add(num1)(num2)', function () {
    var add = function (a) {
      return function (b) {
        return a + b;
      };
    };
    assert.equal(add(2)(3), 5);
  });

— «But what is we know the first addend beforehand and the second one will be known later? Then what?» I think that they’re talking about currying, so I wrote the following:

var add3 = add(3);
    assert.equal(add3(4), 7);
    assert.equal(add3(5), 8);

Suddenly, two more people come in and start asking me and yelling something, thus, preventing me from focusing. They want to look at the way I think. The most interesting part is coming.

They want to know, what would I do to add three or even four numbers? I reply that we would have to memorize the state, something like the following:

it ('should take random number of digits', function () {
    var add = function (a) {
      var sum = a;
      var inner = function (b) {
        if (b) {
          sum += b;
          return inner;
        } else {
          return sum;
        }
      };
      return inner;
    };
    assert.equal(add(2)(3)(), 5);
    assert.equal(add(2)(3)(6)(), 11);
  });

They wonder what I use if for. I use it for the internal function to know the way it will be called: either in a chain or at the very end and could return itself or a number. They seem satisfied for now. «But what if partial application is necessary?» And I write:

var add2 = add(2);
    assert.equal(add2(6)(), 8);

They wonder if I can get rid of a pair of empty brackets at the end. I thought about it… The function should be able to understand, in which context it is being called. But there’s .valueOf! Besides, it will help me to get rid of the unnecessary if. Sure, not a big deal:

var add = function (a) {
      var sum = a;
      var inner = function (b) {
        sum += b;
        return inner;
      };
      inner.valueOf = function () {
        return sum;
      };
      return inner;
    };
    assert.equal(add(3)(4), 7);
    assert.equal(add(3)(5), 8);
    assert.equal(add(9)(-5), 4);
    assert.equal(add(1)(2)(3), 6);  

They want me to apply add2 to another number, for example, 10. So that 2+10=12.

I add a line and get the following:

var add2 = add(2);
    assert.equal(add2(6)(), 8);
    assert.equal(add2(10)(), 12);  

It doesn’t work and returns 18!

I explain that it was meant this way as it memorizes the result of the last adding and uses it for further operations. They want me to fix it so that it would not memorize so obviously. Okay… Want me to provide pure functions? Here’s the chain of conditional identities:

var add = function (orig) {
      var inner = function (val) {
        return add(parseInt(val+'', 10) == val ? inner.captured+val : inner.captured);
      };
      inner.captured = orig;
      inner.valueOf = function () {return inner.captured;};
      return inner;
    };
    assert.equal(add(3)(4), 7);
    assert.equal(add(3)(4)('aa')(5)(), 12);
    var three = add(3);
    var four = add(4);
    assert.equal(three, 3);
    assert.equal(four, 4);
    assert.equal(three(5), 8);
    assert.equal(three(6), 9);
    assert.equal(three(four), 7);
    assert.equal(three(four)(three(four)), 14);

”What is this last empty line for?” — they ask.

... parseInt(val+'', 10) ... ”I used it to force execution of .valueOf. Since val is a function (which is the case for, say, three(four)), parseInt will not start the mechanism of type conversion that will call.valueOf in the end. As for parseInt(func), it is always NaN.”

They keep silent. I guess they have overlooked the odd assignment. Okay, let’s follow the optimization to its logical end:

var add = function (orig) {
      var inner = function (val) {
        return add(parseInt(val+'', 10) == val ? orig+val : orig);
      };
      inner.valueOf = function () {return orig;};
      return inner;
    };

Nice and minimalistic. Tests are absolutely the same.

The four-hour long interview turned out to be quite useful. But the result was unsatisfying. Said that I would be bored working for them. They needed people to work from morning till night without any showing off and creative ideas. As for the latter, it’s the task for managers. So I would be bored soon and start looking for a new job and they did not want any stuff turnover.

But why did they invite me to the interview then? It turns out that it was HR to invite me. As for the interviewers, they wanted me to fail the task. “Now you know that you’re too clever for the position. Feeling better?” So, I left.

You will find the complete source code at github.com/nmakarov/excercises.

Comments

  1. I would hire you just because you started with writing unit tests for each step. Otherwise — what a waste of your time talking to these guys!
  2. I know this has been a while, but this seems to return a function instead of a value for me. so I have to do add(2)(2).valueOf to get the value. Is it possible to be able to change it so that it returns the final value?
3,751

Ropes — Fast Strings

Most of us work with strings one way or another. There’s no way to avoid them — when writing code, you’re doomed to concatinate strings every day, split them into parts and access certain characters by index. We are used to the fact that strings are fixed-length arrays of characters, which leads to certain limitations when working with them. For instance, we cannot quickly concatenate two strings. To do this, we will at first need to allocate the required amount of memory, and then copy there the data from the concatenated strings.