这是代码:
const Pipe = (...fns) => fns.reduce((f,g) => (...args) => g(f(...args)));
所以通过(... fns)fns参数成为一个数组吧?在这一部分:
(f,g) => (...args)
args来自哪里?是否有默认的args参数?我无法阅读这部分内容:
(...args) => g(f(...args))
我无法用这种嵌套包裹我的头脑,这里的减少是如此令人困惑。
答案 0 :(得分:5)
你的第一个问题是你正在处理pipe
的错误实现 - 第二个问题是新JavaScript中存在各种扩展语法,并且(对于初学者)并不总是清楚哪一个被使用在哪里
休息参数
rest参数收集数组中函数的提供参数。这取代了JavaScript过去的旧arguments
对象
const f = (...xs) =>
xs
console.log(f()) // []
console.log(f(1)) // [1]
console.log(f(1,2)) // [1,2]
传播论据
spread参数允许您将数组(或任何可迭代的)作为参数传播给函数调用。这取代了Function.prototype.apply
const g = (a,b,c) =>
a + b + c
const args = [1,2,3]
console.log(g(...args)) // 6
为什么pipe
不好
这不是一个完整的函数 - pipe
的域是[Function]
(函数数组),但如果使用一个空的函数数组(TypeError: Reduce of empty array with no initial value
),这个实现将产生错误
可能不会立即明白这会如何发生,但它可能会以各种方式出现。最值得注意的是,当要应用的函数列表是在程序中的其他位置创建并最终为空的数组时,Pipe
会发生灾难性的失败
const foo = Pipe()
foo(1)
// TypeError: Reduce of empty array with no initial value
const funcs = []
Pipe(...funcs) (1)
// TypeError: Reduce of empty array with no initial value
Pipe.apply(null, funcs) (1)
// TypeError: Reduce of empty array with no initial value
Pipe.call(null) (1)
// TypeError: Reduce of empty array with no initial value
重新实现pipe
这是无数的实现之一,但它应该更容易理解。我们使用了rest参数,并使用了spread参数。最重要的是,pipe
始终会返回一个函数
const pipe = (f,...fs) => x =>
f === undefined ? x : pipe(...fs) (f(x))
const foo = pipe(
x => x + 1,
x => x * 2,
x => x * x,
console.log
)
foo(0) // 4
foo(1) // 16
foo(2) // 36
// empty pipe is ok
const bar = pipe()
console.log(bar(2)) // 2
“但我听说递归很糟糕”
好的,所以如果要管理数千个函数,可能会遇到堆栈溢出。在这种情况下,您可以使用原始帖子中的堆栈安全Array.prototype.reduce
(或reduceRight
)。
这次不是在pipe
内做所有事情,而是将问题分解成更小的部分。每个部分都有不同的目的,pipe
现在只关注部件如何组合在一起。
const comp = (f,g) => x =>
f(g(x))
const identity = x =>
x
const pipe = (...fs) =>
fs.reduceRight(comp, identity)
const foo = pipe(
x => x + 1,
x => x * 2,
x => x * x,
console.log
)
foo(0) // 4
foo(1) // 16
foo(2) // 36
// empty pipe is ok
const bar = pipe()
console.log(bar(2)) // 2
“我真的只想了解帖子中的代码”
好的,让我们逐步完成你的pipe
功能,看看发生了什么。因为reduce
会多次调用reduce函数,所以我每次都会使用args
的唯一重命名
// given
const Pipe = (...fns) => fns.reduce((f,g) => (...args) => g(f(...args)));
// evaluate
Pipe(a,b,c,d)
// environment:
fns = [a,b,c,d]
// reduce iteration 1 (renamed `args` to `x`)
(...x) => b(a(...x))
// reduce iteration 2 (renamed `args` to `y`)
(...y) => c((...x) => b(a(...x))(...y))
// reduce iteration 3 (renamed `args` to `z`)
(...z) => d((...y) => c((...x) => b(a(...x))(...y))(...z))
那么当应用该函数时会发生什么?当我们将Pipe(a,b,c,d)
的结果应用于某个参数Q
// return value of Pipe(a,b,c,d) applied to `Q`
(...z) => d((...y) => c((...x) => b(a(...x))(...y))(...z)) (Q)
// substitute ...z for [Q]
d((...y) => c((...x) => b(a(...x))(...y))(...[Q]))
// spread [Q]
d((...y) => c((...x) => b(a(...x))(...y))(Q))
// substitute ...y for [Q]
d(c((...x) => b(a(...x))(...[Q]))
// spread [Q]
d(c((...x) => b(a(...x))(Q))
// substitute ...x for [Q]
d(c(b(a(...[Q])))
// spread [Q]
d(c(b(a(Q)))
所以,就像我们预期的那样
// run
Pipe(a,b,c,d)(Q)
// evalutes to
d(c(b(a(Q))))
额外阅读
我已经就功能构成这个主题做了很多写作。我鼓励你探讨其中一些相关的问题/我已经就功能构成这个主题做了大量的写作。我鼓励您探讨其中一些相关的问题/答案
如果有的话,您可能会在每个答案中看到compose
(或pipe
,flow
等)的不同实现。也许其中一个人会对你的良心说话!
答案 1 :(得分:0)
嗯@naomik打败了我几乎相同的答案,但我想我仍然会分享我必须帮助你解释这个功能如何以一种可能不那么神秘的方式运作。 (也许)
我想你已经知道“...
”是如何运作的(如果你没有,那么naomik的回答应该有助于此:D)
这是同一管道函数的另一个版本,期望重写以更好地解释使用赋值进行的操作只是为了解释这一点。
Array.prototype.reduce多次调用“reducer”toASingleFunction
- 为functionsToPipe
中的每个函数调用一次。 currentFunctionToPipe
首先是x => x + 1
,然后是x => x * 2
,依此类推......
newFunction
的第一个值是theIdentityFunction
,reducer返回另一个函数nextNewFunction
。顾名思义,它将成为下一次调用“reducer”(newFunction
)的下一个toASingleFunction
。
functionsToPipe
中的所有项目都已缩减后,最终newFunction
将返回finalPipeFunction
。
/**
* `x` goes to itself
*/
const theIdentityFunction = x => x;
/**
* the `reducer` that reduces the array of functions into a single function
* using the value from the last function as the input to the next function
*/
const toASingleFunction = (newFunction, currentFunctionToPipe) => {
const nextNewFunction = function(value) { // i'm not using arrow functions here just to explicitly show that `nextNewFunction` is, in fact, a function
const valueFromLastFunction = newFunction(value);
return currentFunctionToPipe(valueFromLastFunction);
}
return nextNewFunction;
};
const pipe = (...functionsToPipe) => {
const finalPipeFunction = functionsToPipe.reduce(toASingleFunction, /* start with */ theIdentityFunction);
return finalPipeFunction;
}
const f = pipe(
x => x + 1,
x => x * 2,
x => x * x
);
console.log(f(2)) // ((2 + 1) * 2) ^ 2 === 36
也许这有帮助?
祝你好运!答案 2 :(得分:0)
为了更好地理解,我已经将代码转换为块级别,如下所示,试图向新手开发人员解释有问题的代码。为了更好地实施, @naomik 有解决方案。
<强> ES6 强>
const Pipe = (...fns) => {
return fns.reduce((f, g) => {
return (...args) => {
return g(f(...args))
}
})
};
等效的ES5实施:
var Pipe = function () {
var fns = [];
for (var _i = 0; _i < arguments.length; _i++) {
fns[_i] = arguments[_i];
}
return fns.reduce(function (f, g) {
return function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return g(f.apply(void 0/*Undefined or can use 'null'*/, args));
};
});
};
逐个细分:(阅读代码注释以便更好地理解)
const inc = (num) => num+1 //Function to increment the number
const dbl = (num) => num*2 //Function double's the number
const sqr = (num) => num*num //Function Square's the number
/*Function breakdown*/
const _pipe = (f, g) => (...args) => g(f(...args)); //Second part
const Pipe = (...fns) => fns.reduce(_pipe); //First part
const incDblSqr = Pipe(inc, dbl, sqr) //Piping all the functions
const result = incDblSqr(2) // Piped function
console.log(result) // ((2+1)*2) ^ 2 = 36
<强>阐释:强>
有问题的代码有助于将结果从一个函数传递到另一个函数。
上述代码的分步流程:
所有这些都是使用可以访问减少参数的闭包来实现的(为了清楚起见,请阅读下面链接的文章)。
<强>结论:强> 有问题的代码有助于管理对前一个函数发出的结果进行操作的n个函数。
Andrew L. Van Slaars的最佳文章: https://vanslaars.io/post/create-pipe-function/
请仔细阅读上述文章以及@naomik解决方案