Цікава задачка для інтерв'ю, каррінг і часткове застосування функції

Ходжу по job interview. Десь нудно, десь весело. Десь цікаво. На одному з таких мене попросили написати функцію, яка вміє складати два числа. Я написав:
 
 
  it ('should add two numbers', function () {
    var add = function (a, b) {
      return a + b;
    };

    assert.equal (add (2,3), 5);
  });
 
 
А якщо, кажуть, сигнатура функції повинна бути типу такий: add (num1) (num2)? Не питання, кажу. Думаючи, що хитрий Буржуяка хоче перевірити, чи знаю я про те, що можна повертати функції з функцій, пишу ось таке:
 
 
  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);
  });
 
 
 
 
А раптом нам перший доданок відомо заздалегідь, а ось друге буде відомо потім, що робити? Ага, думаю, про currying розмова ведуть. Ось:
 
 
    var add3 = add (3);
    assert.equal (add3 (4), 7);
    assert.equal (add3 (5), 8);
 
 
Тут раптом до кімнати набігло ще двоє, і почали вони вчотирьох мене распрашивать, махають руками, кажуть голосно. Не дають зосередитися, хочуть подивитися на те, як я думаю. Почалося найцікавіше.
 
Питають — а раптом потрібно скласти три числа? Або чотири? Кажу, що треба тоді запам'ятовувати стан, приблизно так:
 
 
  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);
  });
 
 
Навіщо, питають, у тебе всередині if є? А щоб внутрішня функція знала, як вона викликається — в ланцюжку або в самому кінці і, відповідно, повертала б себе або число. Гаразд, кажуть, поки ладно. А якщо знову треба часткове застосування? Пишу:
 
 
    var add2 = add (2);
    assert.equal (add2 (6) (), 8);
 
 
А можна, запитують, як-небудь позбутися пари порожніх дужок вконце? Задумався… Це ж функція повинна якось збагнути, в якому контексті її викликають… А, є ж чарівний `. ValueOf`! І від зайвого if заодно можна позбутися. Нумо:
 
 
    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);
 
 
а тепер застосуй-ка цю add2 до іншого числа, скажімо, 10 — і щоб 2 +10 = 12 вийшло. Додаю рядок, отримую:
 
 
    var add2 = add (2);
    assert.equal (add2 (6) (), 8);
    assert.equal (add2 (10) (), 12);
 
 
Не працює! Повертає 18. Це, пояснюю, так і задумано — воно всередині запам'ятовує результат останнього додавання і використовує для подальших операцій. Вони — треба виправити, щоб так явно не запам'ятовується. Добре, кажу. Хочете чистих функцій? Хочете зовсім цікаво? Нате ланцюжок 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);
 
 
А навіщо, запитують, потрібна ось ця порожній рядок:
 
  ... ParseInt (val +'', 10) ...
 
 
Це для примусового запуску `. ValueOf`. Тому що, кажу, якщо `val` — це функція (що вірно для випадку, скажімо, `three (four)`), то `parseInt` не стане запускати механізм перетворення типів, який врешті-решт викличе `. ValueOf`. А `parseInt (func)` — завжди `NaN`.
 
Дивлюся на них — мовчать. Не помітили зайвого присвоювання, значить. Гаразд, треба довести оптимізацію до логічного кінця. Пишу останній варіант:
 
 
    var add = function (orig) {
      var inner = function (val) {
        return add (parseInt (val +'', 10) == val? orig + val: orig);
      };
      inner.valueOf = function () {return orig;};

      return inner;
    };
 
 
Симпатично і мінімалістично. Тести в точності ті ж самі.
 
Взагалі всі чотирьохгодинне інтерв'ю вийшло вельми корисним. Але закінчилося не надто — кажуть, тобі у нас не цікаво буде. Нам потрібні люди, які будуть сидіти з ранку до вечора і робити, що сказано, що не випендріваясь і не креативом. Креативом у нас начальство займається, і у нас його он уже скільки, нових не треба. Так що тобі незабаром стане нудно, будеш шукати собі нову роботу. А нам, кажуть, текучка ні до чого. А що ж тоді, кажу, на інтерв'ю покликали, питання цікаві задавали, задачки вирішували? А, кажуть, кликав тебе відділ кадрів, а ми думали тебе завалити і тоді тобі не так прикро було б — не взяли тому, що дурний. А зараз ось виходить, що не взяли тому, що розумний. Легше тобі від цього, питають?
 
І поїхав я додому…
 
Повний ісходник у вигляді тесту на гітхабе: github.com / nmakarov / excercises

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.