【问题标题】:JavaScript compose functionsJavaScript 组合函数
【发布时间】:2016-08-08 07:51:08
【问题描述】:

我正在阅读一本书,其中包含以下示例:

var composition1 = function(f, g) {
  return function(x) {
    return f(g(x));
  }
};

然后作者写道:“......组合的幼稚实现,因为它没有考虑执行上下文......”

所以首选的功能是:

var composition2 = function(f, g) {
  return function() {
    return f.call(this, g.apply(this, arguments));
  }
}; 

后面是一个完整的例子:

var composition2 = function composition2(f, g) {
    return function() {
        return f.call(this, g.apply(this, arguments));
    }
};

var addFour = function addFour(x) {
    return x + 4;
};

var timesSeven = function timesSeven(x) {
    return x * 7;
};

var addFourtimesSeven2 = composition2(timesSeven, addFour);
var result2 = addFourtimesSeven2(2);
console.log(result2);

有人可以向我解释为什么 composition2 函数是首选函数(也许有一个例子)?

编辑:

与此同时,我尝试按照建议使用方法作为参数,但没有奏效。结果是 NaN:

var composition1 = function composition1(f, g) {
    return function(x) {
        return f(g(x));
    };
};

var composition2 = function composition2(f, g) {
    return function() {
        return f.call(this, g.apply(this, arguments));
    }
};

var addFour = {
    myMethod: function addFour(x) {
        return x + this.number;
    },
    number: 4
};

var timesSeven = {
    myMethod: function timesSeven(x) {
        return x * this.number;
    },
    number: 7
};

var addFourtimesSeven1 = composition1(timesSeven.myMethod, addFour.myMethod);
var result1 = addFourtimesSeven1(2);
console.log(result1);

var addFourtimesSeven2 = composition2(timesSeven.myMethod, addFour.myMethod);
var result2 = addFourtimesSeven2(2);
console.log(result2);

【问题讨论】:

  • 这其实和currying无关。
  • 你说得对,我改了措辞。
  • 叫做FUNCTION合成!看在老天的份上,你为什么要编写方法?函数组合是指将纯函数以柯里化的形式组合起来,没有任何副作用:const compose = f => g => x => f(g(x))。 Curried 函数,只有一个参数(如我们的 compose 函数)。函数组合只适用于柯里化函数,因为函数只返回一个值。
  • 然后作者写道:“......组合的幼稚实现,因为它没有考虑执行上下文......” 我认为作者很幼稚。

标签: javascript function-composition


【解决方案1】:

这只是回答 composition2 的实际作用:

composition2 用于将this 作为函数本身的上下文。以下示例显示使用data.adata.b 的结果为60

'use strict';

var multiply = function(value) {
    return value * this.a;
}
var add = function(value) {
    return value + this.b;
}

var data = {
    a: 10,
    b: 4,
    func: composition2(multiply, add)
};

var result = data.func(2);
// uses 'data' as 'this' inside the 'add' and 'multiply' functions
// (2 + 4) * 10 = 60

但是,它仍然破坏了以下示例(不幸的是):

'use strict';

function Foo() {
    this.a = 10;
    this.b = 4;
}
Foo.prototype.multiply = function(value) {
    return value * this.a;
};
Foo.prototype.add = function(value) {
    return value + this.b;
};


var foo = new Foo();

var func = composition2(foo.multiply, foo.add);
var result = func(2); // Uncaught TypeError: Cannot read property 'b' of undefined

因为composition2 (this) 的上下文是未定义的(并且不会以任何其他方式调用,例如.apply.callobj.func()),您最终会得到@987654334 @ 在函数中也未定义。

另一方面,我们可以使用下面的代码给它另一个上下文:

'use strict';
var foo = new Foo();

var data = {
    a: 20,
    b: 8,
    func: composition2(foo.multiply, foo.add)
}

var result = data.func(2); 
// uses 'data' as 'this'
// (2 + 8) * 10 = 200 :)

或者通过显式设置上下文:

'use strict';

var multiply = function(value) {
    return value * this.a;
};
var add = function(value) {
    return value + this.b;
};


var a = 20;
var b = 8;

var func = composition2(multiply, add);

// All the same
var result1 = this.func(2);
var result2 = func.call(this, 2);
var result3 = func.apply(this, [2]);

【讨论】:

  • 我希望这是有道理的,如果它没有让我知道的话。如果需要,我会尝试改写。
  • 好吧,你可以这样做,但是你会积极地反对你正在使用的语言。看看我的回答,为什么做这样的事情没有意义;虽然你可以做到。
  • 我将把这个答案作为composition2 实际作用的参考,但我同意你的看法。 :)
  • @ Caramiriel:我以以下方式理解您的回答,希望这是正确的:) 您的第一个示例:composition2 是通过对象调用的,因此 this = object(在本例中为“数据”)你的第二个例子:composition2 被直接调用,因此 this = window object(在这种情况下,'undefined' 因为严格模式)你的第三个例子:composition2 通过一个对象再次调用,因此 this = object(在这种情况下'data')您的第四个示例:直接调用 composition2,因此 this = window 对象,并且因为 'a' 和 'b' 是它工作的窗口对象的属性
  • @JShinigami 没错:)
【解决方案2】:

composition1 不会将第一个以外的参数传递给 g()

如果你这样做:

var composition1 = function(f, g) {
  return function(x1, x2, x3) {
    return f(g(x1, x2, x3));
  }
};

该函数适用于前三个参数。但是,如果您希望它适用于任意数字,则需要使用 Function.prototype.apply

f.call(...) 用于设置this,如 Caramiriel 的回答所示。

【讨论】:

    【解决方案3】:

    我不同意作者的观点。

    想想函数组合的用例。大多数时候,我将函数组合用于转换器函数(纯函数;参数输入,结果输出和this 无关紧要)。

    第二。他使用arguments 的方式会导致错误的做法/死胡同,因为这意味着函数g() 可能依赖于多个参数。

    这意味着,我创建的组合不再是可组合的,因为它可能无法获得所需的所有参数。
    阻止构图的构图;失败

    (并且作为副作用:将参数对象传递给任何其他函数是性能不行,因为 JS 引擎无法再优化它)

    看一下partial application的话题,在JS中通常被错误引用为currying,基本上是:除非传递所有参数,否则函数返回另一个函数,该函数接受剩余的args;直到我有我需要处理的所有论点。

    然后您应该重新考虑实现参数顺序的方式,因为当您将它们定义为 configs-first、data-last 时,这最有效。
    示例:

    //a transformer: value in, lowercased string out
    var toLowerCase = function(str){
        return String(str).toLowerCase();
    }
    
    //the original function expects 3 arguments, 
    //two configs and the data to process.
    var replace = curry(function(needle, heystack, str){
        return String(str).replace(needle, heystack);
    });
    
    //now I pass a partially applied function to map() that only 
    //needs the data to process; this is really composable
    arr.map( replace(/\s[A-Z]/g, toLowerCase) );
    
    //or I create another utility by only applying the first argument
    var replaceWhitespaceWith = replace(/\s+/g);
    //and pass the remaining configs later
    arr.map( replaceWhitespaceWith("-") );
    

    一种稍有不同的方法是创建函数,按照设计,这些函数并非旨在让所有参数在一个步骤中传递,而是一个一个地(或以有意义的组)传递

    var prepend = a => b => String(a) + String(b);  //one by one
    var substr = (from, to) => value => String(str).substr(from, to);  //or grouped
    
    arr.map( compose( prepend("foo"), substr(0, 5) ) );
    arr.map( compose( prepend("bar"), substr(5) ) );
    //and the `to`-argument is undefined; by intent
    

    我不打算使用所有参数调用此类函数,我只想传递它们的配置,并获得一个对传递的数据/值执行工作的函数。

    我总是写someString.substr(0, 5),而不是substr(0, 5, someString),那么为什么要努力使最后一个参数(数据)在第一次调用中适用?

    【讨论】:

      猜你喜欢
      • 2015-05-03
      • 2022-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-09
      • 2020-01-28
      相关资源
      最近更新 更多