JavaScript 组合函数
JavaScript compose functions
我正在阅读一本书,其中包含以下示例:
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);
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 的回答所示。
这只是回答了 composition2
的实际作用:
composition2
当您想将 this
保留为函数本身的上下文时使用。以下示例显示使用 data.a
和 data.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
、.call
或 obj.func()
),你最终会得到 this
在函数中也是未定义的。
另一方面,我们可以使用以下代码为其提供另一个上下文:
'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]);
我不同意作者的观点。
想想功能组合的用例。大多数时候,我将函数组合用于转换函数(纯函数;参数输入、结果输出和 this
无关紧要)。
第二。按照他的方式使用 arguments
会导致糟糕的 practice/dead 结局,因为这意味着函数 g()
可能依赖于多个参数。
这意味着,我创建的组合不再是可组合的,因为它可能无法获得所需的所有参数。
阻止组合的组合;失败
(作为副作用:将参数对象传递给任何其他函数是性能上的禁区,因为 JS 引擎无法再对其进行优化)
看看partial application
的话题,在JS中通常被误引用为currying
,基本上是:除非传递所有参数,否则函数returns另一个函数接受剩余的参数;直到我有我需要处理的所有参数。
那么您应该重新考虑实现参数顺序的方式,因为当您将它们定义为配置优先、数据最后时,这种方式效果最好。
示例:
//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
我不打算用所有参数调用此类函数,我只想传递它们的配置,并获得一个函数来完成传递的 data/value.
而不是 substr(0, 5, someString)
,我总是写 someString.substr(0, 5)
,那么为什么要努力使最后一个参数(数据)在第一次调用中适用?
我正在阅读一本书,其中包含以下示例:
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);
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 的回答所示。
这只是回答了 composition2
的实际作用:
composition2
当您想将 this
保留为函数本身的上下文时使用。以下示例显示使用 data.a
和 data.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
、.call
或 obj.func()
),你最终会得到 this
在函数中也是未定义的。
另一方面,我们可以使用以下代码为其提供另一个上下文:
'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]);
我不同意作者的观点。
想想功能组合的用例。大多数时候,我将函数组合用于转换函数(纯函数;参数输入、结果输出和 this
无关紧要)。
第二。按照他的方式使用 arguments
会导致糟糕的 practice/dead 结局,因为这意味着函数 g()
可能依赖于多个参数。
这意味着,我创建的组合不再是可组合的,因为它可能无法获得所需的所有参数。
阻止组合的组合;失败
(作为副作用:将参数对象传递给任何其他函数是性能上的禁区,因为 JS 引擎无法再对其进行优化)
看看partial application
的话题,在JS中通常被误引用为currying
,基本上是:除非传递所有参数,否则函数returns另一个函数接受剩余的参数;直到我有我需要处理的所有参数。
那么您应该重新考虑实现参数顺序的方式,因为当您将它们定义为配置优先、数据最后时,这种方式效果最好。
示例:
//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
我不打算用所有参数调用此类函数,我只想传递它们的配置,并获得一个函数来完成传递的 data/value.
而不是 substr(0, 5, someString)
,我总是写 someString.substr(0, 5)
,那么为什么要努力使最后一个参数(数据)在第一次调用中适用?