JavaScript-карри

Я новичок в JavaScript, пытаясь понять этот урок о каррировании из Cookie Cookie от Oreilly.

Может ли кто-то быть достаточно любезен, чтобы подробно объяснять эту программу шаг за шагом на простом языке. Обязательно объясните аргумент "null", переданный во второй последней строке программы. Заранее спасибо, если вы можете помочь.

function curry(fn, scope) {
    scope = scope || window;
    var args = [];
    for (var i = 2, len = arguments.length; i < len; ++i) {
        args.push(arguments[i]);
    }
    return function() {
        var args2 = [];
        for (var i = 0; i < arguments.length; i++) {
            args2.push(arguments[i]);
        }
        var argstotal = args.concat(args2);
        return fn.apply(scope, argstotal);
    };
}

function diffPoint(x1, y1, x2, y2) {
    return [Math.abs(x2 - x1), Math.abs(y2 - y1)];
}

var diffOrigin = curry(diffPoint, null, 3.0, 4.0);
var newPt = diffOrigin(6.42, 8.0); //produces array with 3

Ответ 1

// define the curry() function
function curry(fn, scope) {

    // set the scope to window (the default global object) if no scope was passed in.
    scope = scope || window;

    // Convert arguments into a plain array, because it is sadly not one.
    // args will have all extra arguments in it, not including the first 2 (fn, scope)
    // The loop skips fn and scope by starting at the index 2 with i = 2
    var args = [];
    for (var i = 2, len = arguments.length; i < len; ++i) {
        args.push(arguments[i]);
    }

    // Create the new function to return
    return function() {

        // Convert any arguments passed to the this function into an array.
        // This time we want them all
        var args2 = [];
        for (var i = 0; i < arguments.length; i++) {
            args.push(arguments[i]);
        }

        // Here we combine any args originally passed to curry, with the args
        // passed directly to this function.
        //   curry(fn, scope, a, b)(c, d)
        // would set argstotal = [a, b, c, d]
        var argstotal = args.concat(args2);

        // execute the original function being curried in the context of "scope"
        // but with our combined array of arguments
        return fn.apply(scope, argstotal);
    };
}

// Create a function to be curried
function diffPoint(x1, y1, x2, y2) {
    return [Math.abs(x2 - x1), Math.abs(y2 - y1)];
}

// Create a curried version of the diffPoint() function
//   arg1: the function to curry
//   arg2: the scope (passing a falsy value causes the curry function to use window instead)
//   arg3: first argument of diffPoint() to bake in (x1)
//   arg4: second argument of diffPoint() to bake in (y1)
var diffOrigin = curry(diffPoint, null, 3.0, 4.0);

// Call the curried function
// Since the first 2 args where already filled in with the curry, we supply x2 and y2 only
var newPt = diffOrigin(6.42, 8.0);

В этом случае аргумент scope не используется вообще. scope устанавливает объект this. Функция, которую вы просматриваете, не использует this, поэтому она не имеет реального эффекта. Область задается при вызове fn.apply(scope, args), который обе задает область запуска и предоставляет аргументы для доступа.

Ответ 2

Если вы не возражаете против предложения, начните с Javascript: The Good Parts. Следуйте за этим с помощью Javascript Patterns или Secrets of Javascript Ninja для более сложных методов. Поваренные книги больше подходят для консервированных решений проблем, а затем для учебного ресурса.

Мэтт Болл отлично поработал, объясняя, что происходит. Если вы новичок, я бы не стал пот, пытаясь выяснить, что такое карри. В стороне, ИМО, эта функция карри ужасна. Вот как я его изменил.

// this is doing binding and partial function application, 
// so I thought bind was a more appropriate name
// The goal is that when you execute the returned wrapped version of fn, its this will be scope
function bind(fn, scope) {
  // arguments is an implicit variable in every function that contains a full list
  // of what was passed in. It is important to note that javascript doesn't enforce arity.
  // since arguments is not a true array, we need to make it one.
  // a handy trick for this is to use the slice function from array,
  // since it will take arguments, and return a real array.
  // we are storing it in a variable, because we will need to use it again.
  var slice =  Array.prototype.slice,
      // use slice to get an array of all additional arguments after the first two
      // that have been passed to this function.
      args = slice.call(arguments, 2);

  // we are returning a function mostly as a way to delay the execution.
  // as an aside, that this is possible in a mainstream language is a minor miracle
  // and a big part of why i love javascript.
  return function() {
    // since functions are objects in javascript, they can actually have methods.
    // this is one of the built in ones, that lets you execute a function in a different
    // context, meaning that the this variable inside the 
    // function will actually refer to the first argument we pass in.

    // the second argument we are jamming together the arguments from the first function
    // with the arguments passed in to this wrapper function, and passing it on to fn.
    // this lets us partially apply some arguments to fn when we call bind.
    return fn.apply(scope, args.concat(slice.call(arguments)));
  }
}

JavaScript, хотя и замечательный, ужасно многословный. Излишне повторять var при определении ваших привязок просто добавляет много шума. Кроме того, нет необходимости мучительно строить настоящий массив, подобный этому, срез будет принимать аргументы и возвращает вам реальный массив. Особенно в этом случае, когда мы используем его дважды, И мы действительно хотим разрезать первые два аргумента. Наконец, когда вы применяете и ваш первый аргумент имеет значение null, JavaScript применит к вам глобальный объект. Нет необходимости делать это явно.

IMO мое 5-строчное функциональное тело пинает дерьмо из 11 линий, а IMO - гораздо читаемо.

Ответ 3

Функция curry позволяет bind использовать функцию f (первый параметр curry) для области c (второй параметр), с дополнительными дополнительными аргументами (остальные параметры).

Это означает, что вызов этой функции:

curry(func, scope);

возвращает функцию newFunc, вызов которой:

var newFunc = curry(func, scope); // get the new function
newFunc(); // now invoke it

эквивалентно этому:

scope.func();

Чистый эффект всего этого заключается в this ключевое слово, чтобы ссылаться на scope * внутри func.


Время конкретного примера

Скажем, что scope - простой объект JS с одним свойством:

var scope = {name: 'Inigo Montoya'};

и что f - это функция, которая хочет использовать некоторое значение внутри scope:

function f() {
    return 'My name is ' + scope.name;
}

и назовите его, например:

f(); // returns 'My name is Inigo Montoya'

Ну, это один из способов сделать это. Он работает.

Другой способ сделать это - использовать функцию curry. Вместо f, который должен знать для ссылки на объект scope, scope теперь является контекстом вызова функции. Теперь функция может использовать ключевое слово this!

function f_new() {
    return 'My name is ' + this.myName; // see the difference?
}

var sayIt = curry(f, scope);

Теперь sayIt - это функция, которая не заботится о том, что вызывается scope. Он как sayIt определяется на объекте scope, например:

var scope = { name: 'Inigo Montoya',
              sayIt: f_new }

... за исключением того, что на самом деле не определено scope. sayIt работает именно так. Теперь мы можем вызвать sayIt, например:

sayIt(); // returns 'My name is Inigo Montoya'

Еще со мной?

Уф. Суть всего в том, что в вашем примере null предоставляется как область для diffOrigin для запуска, потому что ему не важно, что в области. Строка (в curry) scope = scope || window; означает, что если scope является значением фальшивки (которое null), тогда новая функция (diffOrigin в этом случае) будет выполняться в глобальной области: this будет ссылаться на window.


Это имеет смысл для вас?


*, который называется "контекстом вызова"

Ответ 4

Squeegy опубликовал хорошую разбивку, но я подумал, что добавлю и мою.

//Things to note, 'arguments' is a special variable in javascript that holds 
//an array like object full of all the things passed into a function.
//You can test this out with a function like this:
//var alertArgs = function(){alert(arguments);};

function curry(fn, scope) {
    //Either use the passed in 'scope' object, or the window object as your scope
    scope = scope || window;
    //Create a new array for storing the arguments passed into this function
    var args = [];
    //Loop through the extra arguments (we start at '2' because the first two
    //arguments were stored in `fn` and `scope` respectively.
    //We store these in the temporary 'args' array.
    //(in the example, args will end up looking like: [3.0, 4.0])
    for (var i = 2, len = arguments.length; i < len; ++i) {
        args.push(arguments[i]);
    }
    //We return the 'curried' function
    return function() {
        //This array is not used. I assume it is an error.
        var args2 = [];
        //We now have a new set of arguments, passed in to the curried function
        //We loop through these new arguments, (in the example, 6.42 and 8.0)
        //and add them to the arguments we have already saved. In the end, we have
        //the args array looking like: [3.0, 4.0, 6.42, 8.0]
        for (var i = 0; i < arguments.length; i++) {
            args.push(arguments[i]);
        }
        //This line isn't needed, because args2 is always blank.
        var argstotal = args.concat(args2);

        //Finally we call the function, passing in the full array of arguments
        return fn.apply(scope, argstotal);
    };
}

//This function takes 4 arguments
function diffPoint(x1, y1, x2, y2) {
    return [Math.abs(x2 - x1), Math.abs(y2 - y1)];
}

//We partially apply the first 2 arguments, so x1 is always 3.0, 
//and y1 is always 4.0
var diffOrigin = curry(diffPoint, null, 3.0, 4.0);

//We can now call 'diffPoint' indirectly, without having to specify 
//3.0, 4.0 as the first 2 arguments.
var newPt = diffOrigin(6.42, 8.0); //produces array with 3