繁体   English   中英

如何使Javascript与curring和函数组成协调一致

[英]How to reconcile Javascript with currying and function composition

我喜欢curring,但是有很多原因导致Java开发人员拒绝了该技术:

  1. 对典型咖喱图案的美学关注: f(x) (y) (z)
  2. 由于函数调用数量增加而导致的性能损失
  3. 由于嵌套了许多匿名函数,因此担心调试问题
  4. 担心无点样式的可读性(与合成有关)

有没有一种方法可以减轻这些担忧,以免我的同事讨厌我?

注意: @ftor回答了他/她自己的问题。 这是该答案的直接伴侣。

你已经是天才了

我认为您可能已经重新想象了partial功能–至少部分!

const partial = (f, ...xs) => (...ys) => f(...xs, ...ys);

这是对立的, partialRight

const partialRight = (f, ...xs) => (...ys) => f(...ys, ...xs);

partial接受一个函数,一些args( xs ),并且总是返回一个接受更多args( ys )的函数,然后将f应用于(...xs, ...ys)


初步评论

这个问题的背景是如何在大量的编码员用户群中如何使curry和composition发挥作用。 我的发言将在相同的背景下

  • 仅仅因为一个函数可能返回一个函数并不意味着它就被咖喱了 –用_表示函数正在等待更多的args令人困惑。 回想一下,currying(或部分函数应用程序)抽象了arity,因此我们永远不知道函数调用何时会导致计算值或另一个函数等待被调用。

  • curry 应该翻转参数; 这会给您的编码员带来一些严重的wtf时刻

  • 如果我们要为reduce创建包装器,则reduceRight包装器应该是一致的–例如,您的文件foldl使用f(acc, x, i)但您的文件foldr使用f(x, acc, i) –这将导致很多对这些选择不熟悉的同事之间的痛苦

在接下来的部分,我会代替你的composablepartial ,取出_ -suffixes,并修复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替代品,但是还解决了一些其他的极端情况。 让我们看看这与您的初始清单有何关系

  1. 美学考虑:避免f (x) (y) (z)
  2. 性能:不确定,但我怀疑性能大致相同
  3. 调试:仍然存在问题,因为partial创建了新功能
  4. 可读性:实际上,我认为这里的可读性很好。 在很多情况下, partial非常灵活,可以删除点

我同意您的看法,完全替代了咖喱函数。 我个人发现,一旦我不再对语法的“丑陋性”做出判断,就很容易采用这种新样式–只是有所不同,而且人们不喜欢不同。

当前流行的方法是将每个多参数函数包装在动态库里函数中。 尽管这有助于解决问题#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 ); 

除了dropdropAndSum仅由多参数或可组合函数组成,但是我们已经实现了与完全咖喱函数相同的表现力-至少在此示例中如此。

您可以看到每个可组合函数都将未固化或其他可组合函数用作参数。 这将提高速度,尤其是对于迭代功能应用程序。 但是,一旦可组合函数的结果再次为函数,这也将受到限制。 查看下面的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仅适用于运算符函数,例如dropsub ,它们分别以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)不需要交换参数,但是您经常会遇到Ar两个以上的参数,因此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行中A我们被迫显式传递f 这是可组合的针对咖喱函数的缺点之一:有时我们需要显式传递数据。 但是,如果您不喜欢无点样式,这实际上是一个优势。

结论

使功能可组合可减轻以下担忧:

  1. 美学考虑(较少使用咖喱图案f(x) (y) (z)
  2. 性能损失(更少的函数调用)

但是,第4点(可读性)仅稍有改善(较少的无点样式),而第3点(调试)则根本没有。

尽管我确信完全咖喱化的方法优于此处介绍的方法,但我认为值得考虑的是可组合的高阶函数。 只要您或您的同事对适当的欺骗感到不舒服,就使用它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM