javascript - 给定一个函数 pipe(foo, bar, baz)(1, 2, 3),你如何实现它等同于 javascript 中的 baz(bar(foo(1,2,3))

标签 javascript functional-programming currying

我目前正在学习 JavaScript。 我遇到了这个问题,并尝试在 javacript 中使用柯里化(Currying)来解决它,但无法完全正确地解决它。

给定一个函数 pipe(),它接受多个函数作为参数并返回一个新函数,该函数会将其参数传递给第一个函数,然后将结果传递给第二个函数,然后传递给第三个函数,依此类推,返回最后一个函数的输出。 因此给定:例如,pipe(foo, bar, baz)(1, 2, 3) 等价于 baz(bar(foo(1,2,3)))

我将如何在 javascript 中解决这个问题?

最佳答案

函数式编程很有趣,所以我会试试这个。一般这个概念叫做function composition最好用在一元函数(只接受一个参数的函数)上。

在最基本的层面上,函数组合看起来像这样

const comp = f => g => x => f (g (x))

组成函数的列表是该列表的折叠,使用以id 函数开头的comp .

const id = x => x;

const foldl = f => y => xs =>
  xs.length === 0 ? y : foldl (f) (f (y) (xs[0])) (xs.slice(1));

const compN = fs => foldl (comp) (id) (fs);

我们可以使用

let foo = x => x - 1;
let bar = x => x * 20;
let baz = x => x + 3;

compN ([foo, bar, baz]) (2);
//=> 99

那么它是如何工作的呢?

compN ([foo, bar, baz])
// returns: x => foo(bar(baz(x)))

因此,当我们对该返回值调用 (2) 时,2 将作为 x 传入,然后执行整个链

foo(bar(baz(2)))
foo(bar(5))
foo(100)
//=> 99

作为此实现的结果,您将获得 compN 以及 3 其他可重用函数,idcomp , 和 foldl


但是,您的问题在编写时考虑到了一些魔力。您的标准表明函数组合应该在入口处采用多个参数,因此这种通过引入丑陋的异常来混淆整个概念。不管怎样,这里有一种你可以写的方法

const id = x => x;

const foldl = f => y => xs =>
  xs.length === 0 ? y : foldl (f) (f (y) (xs[0])) (xs.slice(1));

const badComp = f => g => (...xs) => f(g(...xs));

const pipe = fs => foldl (badComp) (id) (fs);

pipe ([x => x * x, (x,y) => x + y]) (2,3); //=> 25

//=> (2+3) * (2+3)
//=> 5 * 5
//=> 25

如您所见,这里的实用性甚至没有很大提高。如果你想向一个函数发送多个参数,你就不会将它们作为数组发送。因此,不要使用 foo(1,2),而是使用 foo([1,2])。这样您就可以使用我的回答第一部分中描述的更受欢迎的一元函数组合。那么你就不必像剩余参数 (...xs) => 和展开运算符 f(g(...xs)) 那样依赖牙痛糖了。

此外,ES6 为您提供了解构赋值,因此您可以轻松地使用一元函数组合方法,并且仍然可以编写看似采用多个参数的方法。 pipe 示例可以使用 compN 重写

compN ([x => x * x, ([x,y]) => x + y]) ([2,3]);

是的,我的观点显然支持经典的一元函数组合,但您可以使用让您开心或完成家庭作业的任何一个。

无论如何,那是我自己的 2 美分。如果您有任何疑问,我很乐意为您提供帮助。


我不想用 compN 的初始实现让您不知所措,所以我使 foldl 函数保持简单。如果我要执行此操作,我实际上会更进一步。

const id = x => x;
const comp = f => g => x => f (g (x));
const eq = x => y => y === x;
const prop = x => y => y[x];
const len = prop ('length');
const isEmpty = comp (eq (0)) (len);
const first = xs => xs[0];
const rest = xs => (xs) .slice (1);
const foldl = f => y => xs =>
  isEmpty (xs) ? y : foldl (f) (f (y) (first (xs))) (rest (xs));
const compN = fs => foldl (comp) (id) (fs);

当然它的工作原理是一样的

compN ([x => x - 1, x => x * 20, x => x + 3]) (2) //=> 99

作为此 compN 实现的结果,我们 免费 获得了 9 个其他完全可重用的函数。那是巨大的,imo。这意味着下次您必须定义另一个函数时,您可能已经完成了由这些其他函数定义的大部分工作。

此外,我选择实现 foldl 而不是依赖于原生的 Array.prototype.reduce 因为它不仅向您展示了如何通过递归实现迭代循环,而且还因为它需要一个柯里化(Currying)过程。

我的 foldl 使用原生 Array.prototype.reduce 的等价物是

var uncurry = f => (x,y) => f (x) (y);
var foldl2 = f => y => xs => xs.reduce(uncurry(f), y);

哪个都好。再次,选择你喜欢的。如果您最终选择了 Array.prototype.reduce 解决方案,至少您现在知道如何制作自己的^,^

非常激进的东西。好的,祝你好运,再见。

关于javascript - 给定一个函数 pipe(foo, bar, baz)(1, 2, 3),你如何实现它等同于 javascript 中的 baz(bar(foo(1,2,3)),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34363381/

相关文章:

javascript 只允许一些滚动

C++:如何做Scheme的 "map"

C++11:lambda,柯里化(Currying)

Javascript 点击、点击和悬停

javascript - 禁用缓存后,Chrome CORS 请求会更快吗?

c - C 中的高阶函数

haskell 初学者 - 递归递归

具有自动柯里化(Currying)功能的 Groovy 闭包

Haskell,是否有可能创建一个可以 curry 任意数量的元组元素的 curry 函数

javascript - 如何使用 React Router 在 React 中不闪烁地重定向?