我喜欢currying,但有几个原因可以解释为什么一个Javascript开发者拒绝这种技术:
f(x) (y) (z)
是否有一种方法可以缓解这些问题,以便我的同事不讨厌我?
答案 0 :(得分:2)
注意: @ftor回答了他/她自己的问题。这是答案的直接伴侣。
你已经是天才了
我认为你可能已经重新设想了partial
功能 - 至少部分是这样!
const partial = (f, ...xs) => (...ys) => f(...xs, ...ys);
和它的对手,partialRight
const partialRight = (f, ...xs) => (...ys) => f(...ys, ...xs);
partial
接受一个函数,一些args(xs
),总是返回一个带有更多args(ys
)的函数,然后应用{ {1}}至f
初步评论
这个问题的背景是关于currying和组合如何与大量的编码用户群一起发挥作用。我的评论将在同一背景下
仅仅因为函数可能返回一个函数并不意味着它是 curry - 在(...xs, ...ys)
上添加表示函数正在等待更多args令人困惑。回想一下currying(或部分函数应用程序)抽象arity,所以我们永远不知道函数调用何时会导致计算值或其他函数等待调用。
_
应该不翻转参数;这会给你的编码员带来一些严重的重要时刻
如果我们要为curry
创建包装器,reduce
包装器应该保持一致 - 例如,reduceRight
使用foldl
但是您的f(acc, x, i)
使用foldr
- 这会给那些不熟悉这些选择的同事带来很多痛苦
对于下一部分,我将使用f(x, acc, i)
替换您的composable
,删除partial
- 后缀,并修复_
包装
可组合功能
foldr

程序化解决方案
const partial = (f, ...xs) => (...ys) => f(...xs, ...ys);
const partialRight = (f, ...xs) => (...ys) => f(...ys, ...xs);
const comp = (f, g) => x => f(g(x));
const foldl = (f, acc, xs) => xs.reduce(f, acc);
const drop = (xs, n) => xs.slice(n);
const add = (x, y) => x + y;
const sum = partial(foldl, add, 0);
const dropAndSum = comp(sum, partialRight(drop, 1));
console.log(
dropAndSum([1,2,3,4]) // 9
);

更严肃的任务
const partial = (f, ...xs) => (...ys) => f(...xs, ...ys);
// restore consistent interface
const foldr = (f, acc, xs) => xs.reduceRight(f, acc);
const comp = (f,g) => x => f(g(x));
// added this for later
const flip = f => (x,y) => f(y,x);
const I = x => x;
const inc = x => x + 1;
const compn = partial(foldr, flip(comp), I);
const inc3 = compn([inc, inc, inc]);
console.log(
inc3(0) // 3
);

部分权力!
const partial = (f, ...xs) => (...ys) => f(...xs, ...ys);
const filter = (f, xs) => xs.filter(f);
const comp2 = (f, g, x, y) => f(g(x, y));
const len = xs => xs.length;
const odd = x => x % 2 === 1;
const countWhere = f => partial(comp2, len, filter, f);
const countWhereOdd = countWhere(odd);
console.log(
countWhereOdd([1,2,3,4,5]) // 3
);
实际上可以根据需要多次应用
partial

这使它成为使用可变功能的不可或缺的工具
const partial = (f, ...xs) => (...ys) => f(...xs, ...ys)
const p = (a,b,c,d,e,f) => a + b + c + d + e + f
let f = partial(p,1,2)
let g = partial(f,3,4)
let h = partial(g,5,6)
console.log(p(1,2,3,4,5,6)) // 21
console.log(f(3,4,5,6)) // 21
console.log(g(5,6)) // 21
console.log(h()) // 21

最后,演示const partial = (f, ...xs) => (...ys) => f(...xs, ...ys)
const add = (x,y) => x + y
const p = (...xs) => xs.reduce(add, 0)
let f = partial(p,1,1,1,1)
let g = partial(f,2,2,2,2)
let h = partial(g,3,3,3,3)
console.log(h(4,4,4,4))
// 1 + 1 + 1 + 1 +
// 2 + 2 + 2 + 2 +
// 3 + 3 + 3 + 3 +
// 4 + 4 + 4 + 4 => 40
partialRight

<强>摘要强>
好的,所以const partial = (f, ...xs) => (...ys) => f(...xs, ...ys);
const partialRight = (f, ...xs) => (...ys) => f(...ys, ...xs);
const p = (...xs) => console.log(...xs)
const f = partialRight(p, 7, 8, 9);
const g = partial(f, 1, 2, 3);
const h = partial(g, 4, 5, 6);
p(1, 2, 3, 4, 5, 6, 7, 8, 9) // 1 2 3 4 5 6 7 8 9
f(1, 2, 3, 4, 5, 6) // 1 2 3 4 5 6 7 8 9
g(4, 5, 6) // 1 2 3 4 5 6 7 8 9
h() // 1 2 3 4 5 6 7 8 9
几乎可以替代partial
,但也会解决一些额外的极端情况。让我们看看这是如何与你的初始列表相提并论的
composable
f (x) (y) (z)
创建新功能partial
足够灵活,可以在很多情况下删除积分我同意你的观点,即完全咖喱功能无法替代。我个人发现,一旦我不再评价“丑陋”,就会很容易采用新的风格。语法 - 它只是不同,人们不喜欢不同。
答案 1 :(得分:1)
目前流行的方法规定每个多参数函数都包含在动态咖喱函数中。虽然这有助于关注#1,但其余的却没有受到影响。这是另一种方法。
只有在最后一个参数中才能组合一个可组合函数。为了将它们与普通的多参数函数区分开来,我用尾随下划线命名它们(命名很难)。
const comp_ = (f, g) => x => f(g(x)); // composable function
const foldl_ = (f, acc) => xs => xs.reduce((acc, x, i) => f(acc, x, i), acc);
const curry = f => y => x => f(x, y); // fully curried function
const drop = (xs, n) => xs.slice(n); // normal, multi argument function
const add = (x, y) => x + y;
const sum = foldl_(add, 0);
const dropAndSum = comp_(sum, curry(drop) (1));
console.log(
dropAndSum([1,2,3,4]) // 9
);
除了drop
之外,dropAndSum
完全由多参数或可组合函数组成,但我们已经实现了与完全curried函数相同的表达能力 - 至少在这个例子中。
您可以看到每个可组合函数都需要将未处理函数或其他可组合函数作为参数。这将提高速度,特别是对于迭代功能应用。然而,一旦可组合函数的结果再次成为函数,这也是限制性的。请查看下面的countWhere
示例以获取更多信息。
我们可以轻松实现程序化解决方案,而不是手动定义可组合函数:
// generic functions
const composable = f => (...args) => x => f(...args, x);
const foldr = (f, acc, xs) =>
xs.reduceRight((acc, x, i) => f(x, acc, i), acc);
const comp_ = (f, g) => x => f(g(x));
const I = x => x;
const inc = x => x + 1;
// derived functions
const foldr_ = composable(foldr);
const compn_ = foldr_(comp_, I);
const inc3 = compn_([inc, inc, inc]);
// and run...
console.log(
inc3(0) // 3
);
也许您注意到curry
(从第一个示例开始)交换参数,而composable
则没有。 curry
仅适用于drop
或sub
等运算符函数,它们分别以curried和uncurried形式具有不同的参数顺序。运算符函数是任何只需要非函数参数的函数。在这一点......
const I = x => x;
const eq = (x, y) => x === y; // are operator functions
// whereas
const A = (f, x) => f(x);
const U = f => f(f); // are not operator but a higher order functions
高阶函数(HOF)不需要交换参数,但是你会经常遇到高于2的arities,因此composbale
函数很有用。
HOF是函数式编程中最棒的工具之一。它们从功能应用中抽象出来这就是我们一直使用它们的原因。
我们也可以解决更复杂的任务:
// generic functions
const composable = f => (...args) => x => f(...args, x);
const filter = (f, xs) => xs.filter(f);
const comp2 = (f, g, x, y) => f(g(x, y));
const len = xs => xs.length;
const odd = x => x % 2 === 1;
// compositions
const countWhere_ = f => composable(comp2) (len, filter, f); // (A)
const countWhereOdd = countWhere_(odd);
// and run...
console.log(
countWhereOdd([1,2,3,4,5]) // 3
);
请注意,在A
行中,我们被迫明确传递f
。这是针对curried函数组合的缺点之一:有时我们需要显式传递数据。但是,如果你不喜欢无点风格,这实际上是一个优势。
使函数可组合减轻了以下问题:
f(x) (y) (z)
然而,第4点(可读性)只是略有改进(无点无风格)和第3点(调试)根本没有。
虽然我确信完全咖喱的方法优于此处提出的方法,但我认为可组合的高阶函数值得考虑。只要您或您的同事对适当的蜷缩感到不舒服,就使用它们。