Частичное приложение — Eloquent Javascript

Я читаю Eloquent Javascript и мне трудно понять приведенный ниже пример. Кто-нибудь сможет сделать построчное объяснение типа? В частности, я не понимаю, почему первый цикл начинается с единицы и почему метод push используется как для knownArgs, так и для аргументов. Я знаю, что это связано с "частичным применением", но хотелось бы более подробного объяснения того, что именно происходит построчно.

var op = {
 "+": function(a,b){return a + b;}
};

function partial(func) {
 var knownArgs = arguments;

 return function() {
  var realArgs = [];

  for (var i=1; i<knownArgs.length; i++)
   realArgs.push(knownArgs[i]);

  for (var i=0; i<arguments.length; i++)
   realArgs.push(arguments[i]);

  return func.apply(null, realArgs);
 };
}

map(partial(op["+"], 1), [0, 2, 4, 6, 8, 10]);

person KMcA    schedule 11.11.2012    source источник
comment
Мне потребовалось буквально 2 секунды, чтобы найти этот drdobbs.com/open-source/. Прочитать все об этом.   -  person elclanrs    schedule 12.11.2012
comment
Спасибо, я дочитаю. Надеюсь, я все еще могу получить объяснение вышеизложенному.   -  person KMcA    schedule 12.11.2012
comment
Я обновил свой ответ с небольшим объяснением. Так получилось, что всего пару недель назад я провел пару занятий по JavaScript с несколькими примерами, очень похожими на этот :-)   -  person Pointy    schedule 12.11.2012


Ответы (2)


Переменная knownArgs хранит копию значения arguments в том виде, в каком оно было при вызове partial(). Этот вызов возвращает другую функцию, и внутри этого кода arguments — это совершенно другой список, это аргументы, переданные этой возвращаемой функции. Другими словами:

var p = partial(someFunction, "привет", "мир");

Когда вызывается p(), knownArgs будет "hello" и "world" (ну и someFunction тоже, но учтите, что первый цикл начинается с 1). Если вызов p() выглядит так:

p("как", "есть", "ты");

затем он сначала поместит «hello» и «world» в список realArgs (из knownArgs), а затем три параметра, переданные в p(), из arguments.

изменить пошаговое описание того, как оценивается map(partial(op["+"], 1), [0, 2, 4, 6, 8, 10]);:

  1. Во-первых, необходимо оценить op["+"]. Я предполагаю, что он возвращает функцию, возможно, что-то вроде этого:

    function add(a, b) {
      return a + b;
    }
    
  2. Эта функция «добавить» и значение 1 передаются в partial(). Таким образом, внутри partial() псевдомассив arguments выглядит так:

    [ add, 1 ]
    

    То есть первый параметр — это функция «добавить» из op["+"], а второй — это значение 1. Единственное, что действительно делает partial() перед возвратом анонимной функции, — это сохраняет arguments в knownArgs. Это необходимо сделать, потому что странной псевдопеременной arguments всегда присваивается новое значение при каждом вызове функции. Он сохраняется здесь, чтобы код анонимной функции мог получить к нему доступ позже.

  3. Теперь, когда анонимная функция возвращается из partial() и этот массив четных чисел, мы вызываем map(). Эта функция, вероятно, выглядит примерно так (у меня нет книги):

    function map(fn, list) {
      var i, result = [];
      for (i = 0; i < list.length; ++i) {
        result.push( fn( list[i] ) );
      }
      return result;
    }
    

    Таким образом, внутри map() первым параметром является анонимная функция, возвращенная из более раннего вызова partial(). Что делает эта функция? Что ж, он объединяет параметры исходного вызова partial(), кроме первого, с переданными ему параметрами. Функция map() передает только один параметр, поэтому результирующий список параметров при каждом вызове анонимной функции будет представлять собой значение, 1 переданное partial(), а затем, на каждой итерации, другое четное число из списка.

Более простым примером может быть рассмотрение того, что происходит, когда вы вызываете:

partial(op["+"], 1)(2);

То есть, если вы вызываете partial(), а затем сразу же используете его возвращаемое значение (анонимная функция). Эффект будет таким же, как и при вызове:

add(1, 2);
person Pointy    schedule 11.11.2012
comment
Спасибо. Я понимаю ваш ответ и его отношение к приведенному выше коду. Я все еще довольно нечетко понимаю концепцию частичного применения. Помимо ссылки, опубликованной elclanrs, есть ли у вас какие-либо рекомендации по объяснению для кого-то, кто совершенно не знаком с программированием? - person KMcA; 12.11.2012
comment
@KMcA хорошо для тех, кто плохо знаком с программированием, это довольно причудливые вещи :-) Хотя это не очень сложно; это просто набор идей, с которыми вы должны работать, чтобы ознакомиться с ним. Начать с JavaScript — отличная идея, потому что это относительно дружелюбная среда для экспериментов с подобными концепциями. Ключевым моментом является то, что в JavaScript и некоторых других языках функции — это не просто фрагменты кода, но и объекты, и с ними можно обращаться практически так же, как с любыми другими объектами. У них просто есть дополнительная возможность вызывать их как функции! - person Pointy; 12.11.2012
comment
Я рассматривал возможность использования этого в качестве точки остановки в этой книге, так как кажется, что я изучаю некоторые продвинутые концепции, но я знаю, где близок момент, когда я на самом деле использовал бы эти концепции. Когда вы говорите о причудливых вещах, не могли бы вы сказать, что мне лучше сделать резервную копию и сделать некоторое введение в книги / онлайн-курсы по программированию? Например, у меня есть Learn to Program, сидящий здесь на моем столе, но я ненавижу останавливать Eloquent Javascript, не закончив. - person KMcA; 12.11.2012
comment
На самом деле написание кода и понимание того, как что-то идет не так (и правильно), на мой взгляд, очень важно. Однако я не думаю, что вам следует беспокоиться о том, чтобы навредить себе продвинутыми вещами :-) Если серьезно, самое главное, что нужно сделать, это найти несколько не слишком больших проектов и работать над ними. Я хотел бы предложить книги или веб-сайты, которые были бы хороши для этой цели, но я действительно не знаю ни одного. (Я использовал сайт Project Euler для изучения Erlang, но это может быть сложно (или невозможно :-) и, к сожалению, JavaScript не лучший язык для этого.) - person Pointy; 12.11.2012
comment
Все еще парюсь над этим. Я настроил оповещения, чтобы видеть значения, добавляемые с каждым циклом. Я очень визуальный ученик, поэтому мне нравится на самом деле представлять себе, что происходит. Можете ли вы визуально пояснить, как вызов map(partial(op[+], 1), [0, 2, 4, 6, 8, 10]); фактически применяется. Я вижу общую картину, и все это имеет смысл, но мне трудно увидеть, как применяются 1 и массив. Поскольку я настолько визуален, я вижу partial(func) и не могу увидеть связь с 1 и массивом. Любая дополнительная помощь будет очень признательна. - person KMcA; 12.11.2012
comment
@KMcA Я отредактирую ответ с пошаговым описанием того, что происходит. (Я сделаю предположение о том, что делает map(); это, вероятно, типичное значение функционального программирования для карты.) - person Pointy; 12.11.2012
comment
Да, я не включил карту и не добавил функции, но вы были правы. Думаю, теперь я гораздо лучше разбираюсь в этом. Еще раз, спасибо. - person KMcA; 13.11.2012

Первый цикл начинается с единицы вместо нуля, потому что knownArgs[0] содержит функцию, а не ее аргумент.

push добавляет один элемент в массив. Это довольно распространенный способ построения массива.

  var realArgs = [];

  for (var i=1; i<knownArgs.length; i++)
    realArgs.push(knownArgs[i]);

  for (var i=0; i<arguments.length; i++)
    realArgs.push(arguments[i]);

создаст новый массив, объединенный из knownArgs и arguments. knownArgs содержит каррированные аргументы и функцию (которая не добавляется к realArgs), а arguments — это аргументы, передаваемые функции при ее вызове.

person John Dvorak    schedule 11.11.2012