我如何像 Elm 那样在 JS 中传递函数?
How can I pipe functions in JS like Elm does?
我刚开始学习函数式编程,我很难弄清楚如何做到这一点(如果这值得麻烦的话)。我研究了柯里化,但不确定这是否是我需要走的方向??还是管道?
我想从一个值开始,然后通过不同的函数将其通过管道传输。 Underscore 有类似的 'chain' 方法。但是我不想使用原型来做到这一点。我意识到解决方案可能与我的目标语法不匹配。
Elm 具有 |>
语法(如下),非常好看
// what i'd like to do (or similar) in JS *without using prototype*
num = ("(123) 456-7890")
.removeDashes()
.removeParens()
.removeSpaces()
// what elm does
"(123) 456-7890"
|> removeDashes
|> removeParens
|> rem
// functions I wrote so far
removeDashes = function(str) {
return str.replace(/-/g, '');
};
removeParens = function(str) {
return str.replace(/\(|\)/g, '');
};
removeSpaces = function(str) {
return str.replace(/\s/g, '');
};
// what i'm currently doing
num =
removeDashes(
removeParens(
removeSpaces(
"(123) 456-7890"")));
正如最后所说,研究使用原型。字符串原型允许您向所有字符串添加 class 级别的功能:
String.prototype.removeParens = function() {
this = this.replace(/\(|\)/g, '');
}
这可以让你做这样的事情:
var myString = "(test)";
myString.removeParens();
一旦您将其他函数添加到 String 原型中,您就可以像这样简单地链接函数调用:
myString.removeDashes().removeParens().removeSpaces();
等等
最简单的方法是将它们真正添加到原型链中,但您可以使用对象来实现。这是一个简单的例子:
function MyString( str ){
var value = str.toString();
return {
removeDashes: function() {
value = value.replace(/-/g, '');
return this;
},
removeParens: function() {
value = value.replace(/\(|\)/g, '');
return this;
},
removeSpaces: function() {
value = value.replace(/\s/g, '');
return this;
},
toString: function (){
return value;
},
valueOf: function (){
return value;
}
};
}
您可以稍后执行此操作:
var clean = (new MyString('This \(Is)\/ Dirty'))
.removeDashes()
.removeParens()
.removeSpaces();
这样,您的 prototype
就会保持干净。要检索非对象值,只需 运行 toStrong()
方法,toValue()
或对该值执行任何操作(连接 1,除以它,任何操作!)。
正如其他人所说,将函数添加到 String 原型是一个有效且简短的解决方案。但是,如果您不想将它们添加到 String 原型中,或者如果您想在将来执行更复杂的功能,另一种选择是制作一个包装器来处理这个:
function SpecialString(str){
this.str = str;
this.removeDashes = function() {
this.str=this.str.replace(/-/g, '');
return this;
};
this.removeParens = function() {
this.str=this.str.replace(/\(|\)/g, '');
return this;
};
this.removeSpaces = function() {
this.str=this.str.replace(/\s/g, '');
return this;
};
return this;
}
num = new SpecialString("(123) 456-7890").removeDashes().removeParens().removeSpaces();
console.log(num) // 1234567890
这是我在 lodash 中找到的一个解决方案,它允许你混合你自己的函数,然后在链上使用它们:
...
removeSpaces = function(str) {
return str.replace(/\s/g, '');
};
_.mixin({
removeSpaces: removeSpaces,
removeParens: removeParens,
removeDashes: removeDashes
});
num = _.chain("(123) 456-7890")
.removeSpaces()
.removeParens()
.removeDashes()
.value()
有多种方法可以解决这个问题,您已经在下划线和 Elm 中提供了参考。
在 Elm 中,柯里化函数是等式的重要组成部分。由于每个函数都接收一个参数,因此您可以构建其中一些部分应用的链,等待您在管道中编织的参数。这同样适用于 Haskell、PureScript 和同类语言。
在 JavaScript 中重现 ipsis literis 需要一点糖分——您可以使用 sweet.js macro 来获得实现它的源转换。
没有糖,它可以有很多用途。也许探索的一种方法是使用生成器,向下传递已解析链的位,直到获得非函数值。
不是一个非常严肃的建议,但是一个可行的建议:
var update = pipe()(removeDashes >> removeParens >> removeSpaces);
update("(123) 456-7890"); //=> "1234567890"
这是基于 pipe
的实现:
var pipe = function() {
var queue = [];
var valueOf = Function.prototype.valueOf;
Function.prototype.valueOf = function() {
queue.push(this);
return 1;
};
return function() {
Function.prototype.valueOf = valueOf;
return function(x) {
for (var i = 0, val = x, len = queue.length; i < len; i++) {
val = queue[i](val);
}
return val;
}
};
};
您可以在 slide 33 of my talk on functional composition in js 中查看更多内容。
如果您想在 JavaScript 中尝试使用函数式编程,我建议您使用像 Underscore、Lodash 或 Ramda 这样的库。它们都具有 compose/pipe 功能。大多数时候,您希望将它与这些库也提供的某种形式的部分应用程序结合起来。
无论如何,尝试自己实现它是一个很好的练习。
我会这样解决...
/* Asumes es5 or higher */
function pipe (firstFn /* ...restFns */) {
var _ = null;
var _slice = Array.prototype.slice;
var restFns = _slice.call(arguments, 1) || [];
return function exec_fns() {
var args = _slice.call(arguments, 0, 1);
return restFns.reduce(function(acc, fn) {
return fn.call(_, acc);
}, firstFn.apply(_, args));
}
}
removeDashes = function(str) {
return str.replace(/-/g, '');
};
removeParens = function(str) {
return str.replace(/\(|\)/g, '');
};
removeSpaces = function(str) {
return str.replace(/\s/g, '');
};
console.log(pipe(
removeDashes,
removeParens,
removeSpaces
)("(123) 456-7890") == "1234567890")
Also Functional JavaScript 的 Fogus 是深入挖掘这种编程风格的好资源
一行就可以创建管道函数,可读性好:
const pipe = (...fns) => fns.reduce((v, f) => v.constructor === Function ? v() : f(v));
它会这样使用:
var numResult = pipe('(123) 456-7890', removeDashes, removeParens, removeSpaces);
var pipe = (...fns) => fns.reduce((v, f) => v.constructor === Function ? v() : f(v));
function removeDashes(str) {
return str.replace(/-/g, '');
}
function removeParens(str) {
return str.replace(/\(|\)/g, '');
}
function removeSpaces(str) {
return str.replace(/\s/g, '');
}
console.log(
'result:', pipe('(123) 456-7890', removeDashes, removeParens, removeSpaces)
);
注意:此功能需要支持传播运算符的平台...
。
为了以防万一,我为此创建了一个支持异步函数(Promises)的模块,它也适用于 older/legacy 不能使用 spread ...
[= 的平台16=]
我刚开始学习函数式编程,我很难弄清楚如何做到这一点(如果这值得麻烦的话)。我研究了柯里化,但不确定这是否是我需要走的方向??还是管道?
我想从一个值开始,然后通过不同的函数将其通过管道传输。 Underscore 有类似的 'chain' 方法。但是我不想使用原型来做到这一点。我意识到解决方案可能与我的目标语法不匹配。
Elm 具有 |>
语法(如下),非常好看
// what i'd like to do (or similar) in JS *without using prototype*
num = ("(123) 456-7890")
.removeDashes()
.removeParens()
.removeSpaces()
// what elm does
"(123) 456-7890"
|> removeDashes
|> removeParens
|> rem
// functions I wrote so far
removeDashes = function(str) {
return str.replace(/-/g, '');
};
removeParens = function(str) {
return str.replace(/\(|\)/g, '');
};
removeSpaces = function(str) {
return str.replace(/\s/g, '');
};
// what i'm currently doing
num =
removeDashes(
removeParens(
removeSpaces(
"(123) 456-7890"")));
正如最后所说,研究使用原型。字符串原型允许您向所有字符串添加 class 级别的功能:
String.prototype.removeParens = function() {
this = this.replace(/\(|\)/g, '');
}
这可以让你做这样的事情:
var myString = "(test)";
myString.removeParens();
一旦您将其他函数添加到 String 原型中,您就可以像这样简单地链接函数调用:
myString.removeDashes().removeParens().removeSpaces();
等等
最简单的方法是将它们真正添加到原型链中,但您可以使用对象来实现。这是一个简单的例子:
function MyString( str ){
var value = str.toString();
return {
removeDashes: function() {
value = value.replace(/-/g, '');
return this;
},
removeParens: function() {
value = value.replace(/\(|\)/g, '');
return this;
},
removeSpaces: function() {
value = value.replace(/\s/g, '');
return this;
},
toString: function (){
return value;
},
valueOf: function (){
return value;
}
};
}
您可以稍后执行此操作:
var clean = (new MyString('This \(Is)\/ Dirty'))
.removeDashes()
.removeParens()
.removeSpaces();
这样,您的 prototype
就会保持干净。要检索非对象值,只需 运行 toStrong()
方法,toValue()
或对该值执行任何操作(连接 1,除以它,任何操作!)。
正如其他人所说,将函数添加到 String 原型是一个有效且简短的解决方案。但是,如果您不想将它们添加到 String 原型中,或者如果您想在将来执行更复杂的功能,另一种选择是制作一个包装器来处理这个:
function SpecialString(str){
this.str = str;
this.removeDashes = function() {
this.str=this.str.replace(/-/g, '');
return this;
};
this.removeParens = function() {
this.str=this.str.replace(/\(|\)/g, '');
return this;
};
this.removeSpaces = function() {
this.str=this.str.replace(/\s/g, '');
return this;
};
return this;
}
num = new SpecialString("(123) 456-7890").removeDashes().removeParens().removeSpaces();
console.log(num) // 1234567890
这是我在 lodash 中找到的一个解决方案,它允许你混合你自己的函数,然后在链上使用它们:
...
removeSpaces = function(str) {
return str.replace(/\s/g, '');
};
_.mixin({
removeSpaces: removeSpaces,
removeParens: removeParens,
removeDashes: removeDashes
});
num = _.chain("(123) 456-7890")
.removeSpaces()
.removeParens()
.removeDashes()
.value()
有多种方法可以解决这个问题,您已经在下划线和 Elm 中提供了参考。
在 Elm 中,柯里化函数是等式的重要组成部分。由于每个函数都接收一个参数,因此您可以构建其中一些部分应用的链,等待您在管道中编织的参数。这同样适用于 Haskell、PureScript 和同类语言。
在 JavaScript 中重现 ipsis literis 需要一点糖分——您可以使用 sweet.js macro 来获得实现它的源转换。
没有糖,它可以有很多用途。也许探索的一种方法是使用生成器,向下传递已解析链的位,直到获得非函数值。
不是一个非常严肃的建议,但是一个可行的建议:
var update = pipe()(removeDashes >> removeParens >> removeSpaces);
update("(123) 456-7890"); //=> "1234567890"
这是基于 pipe
的实现:
var pipe = function() {
var queue = [];
var valueOf = Function.prototype.valueOf;
Function.prototype.valueOf = function() {
queue.push(this);
return 1;
};
return function() {
Function.prototype.valueOf = valueOf;
return function(x) {
for (var i = 0, val = x, len = queue.length; i < len; i++) {
val = queue[i](val);
}
return val;
}
};
};
您可以在 slide 33 of my talk on functional composition in js 中查看更多内容。
如果您想在 JavaScript 中尝试使用函数式编程,我建议您使用像 Underscore、Lodash 或 Ramda 这样的库。它们都具有 compose/pipe 功能。大多数时候,您希望将它与这些库也提供的某种形式的部分应用程序结合起来。
无论如何,尝试自己实现它是一个很好的练习。 我会这样解决...
/* Asumes es5 or higher */
function pipe (firstFn /* ...restFns */) {
var _ = null;
var _slice = Array.prototype.slice;
var restFns = _slice.call(arguments, 1) || [];
return function exec_fns() {
var args = _slice.call(arguments, 0, 1);
return restFns.reduce(function(acc, fn) {
return fn.call(_, acc);
}, firstFn.apply(_, args));
}
}
removeDashes = function(str) {
return str.replace(/-/g, '');
};
removeParens = function(str) {
return str.replace(/\(|\)/g, '');
};
removeSpaces = function(str) {
return str.replace(/\s/g, '');
};
console.log(pipe(
removeDashes,
removeParens,
removeSpaces
)("(123) 456-7890") == "1234567890")
Also Functional JavaScript 的 Fogus 是深入挖掘这种编程风格的好资源
一行就可以创建管道函数,可读性好:
const pipe = (...fns) => fns.reduce((v, f) => v.constructor === Function ? v() : f(v));
它会这样使用:
var numResult = pipe('(123) 456-7890', removeDashes, removeParens, removeSpaces);
var pipe = (...fns) => fns.reduce((v, f) => v.constructor === Function ? v() : f(v));
function removeDashes(str) {
return str.replace(/-/g, '');
}
function removeParens(str) {
return str.replace(/\(|\)/g, '');
}
function removeSpaces(str) {
return str.replace(/\s/g, '');
}
console.log(
'result:', pipe('(123) 456-7890', removeDashes, removeParens, removeSpaces)
);
注意:此功能需要支持传播运算符的平台...
。
为了以防万一,我为此创建了一个支持异步函数(Promises)的模块,它也适用于 older/legacy 不能使用 spread ...
[= 的平台16=]