简体   繁体   English

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

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

I love currying but there are a couple of reasons why a lof of Javascript devs reject this technique: 我喜欢curring,但是有很多原因导致Java开发人员拒绝了该技术:

  1. aesthetic concerns about the typical curry pattern: f(x) (y) (z) 对典型咖喱图案的美学关注: f(x) (y) (z)
  2. concerns about performance penalties due to the increased number of function calls 由于函数调用数量增加而导致的性能损失
  3. concerns about debugging issues because of the many nested anonymous functions 由于嵌套了许多匿名函数,因此担心调试问题
  4. concerns about readability of point-free style (currying in connection with composition) 担心无点样式的可读性(与合成有关)

Is there an approach that can mitigate these concerns so that my coworkers don't hate me? 有没有一种方法可以减轻这些担忧,以免我的同事讨厌我?

Note: @ftor answered his/her own question. 注意: @ftor回答了他/她自己的问题。 This is a direct companion to that answer. 这是该答案的直接伴侣。

You're already a genius 你已经是天才了

I think you might've re-imagined the partial function – at least, in part! 我认为您可能已经重新想象了partial功能–至少部分!

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

and it's counter-part, partialRight 这是对立的, partialRight

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

partial takes a function, some args ( xs ), and always returns a function that takes some more args ( ys ), then applies f to (...xs, ...ys) partial接受一个函数,一些args( xs ),并且总是返回一个接受更多args( ys )的函数,然后将f应用于(...xs, ...ys)


Initial remarks 初步评论

The context of this question is set in how currying and composition can play nice with a large user base of coders. 这个问题的背景是如何在大量的编码员用户群中如何使curry和composition发挥作用。 My remarks will be in the same context 我的发言将在相同的背景下

  • just because a function may return a function does not mean that it is curried – tacking on a _ to signify that a function is waiting for more args is confusing. 仅仅因为一个函数可能返回一个函数并不意味着它就被咖喱了 –用_表示函数正在等待更多的args令人困惑。 Recall that currying (or partial function application) abstracts arity, so we never know when a function call will result in the value of a computation or another function waiting to be called. 回想一下,currying(或部分函数应用程序)抽象了arity,因此我们永远不知道函数调用何时会导致计算值或另一个函数等待被调用。

  • curry should not flip arguments; curry 应该翻转参数; that is going to cause some serious wtf moments for your fellow coder 这会给您的编码员带来一些严重的wtf时刻

  • if we're going to create a wrapper for reduce , the reduceRight wrapper should be consistent – eg, your foldl uses f(acc, x, i) but your foldr uses f(x, acc, i) – this will cause a lot of pain amongst coworkers that aren't familiar with these choices 如果我们要为reduce创建包装器,则reduceRight包装器应该是一致的–例如,您的文件foldl使用f(acc, x, i)但您的文件foldr使用f(x, acc, i) –这将导致很多对这些选择不熟悉的同事之间的痛苦

For the next section, I'm going to replace your composable with partial , remove _ -suffixes, and fix the foldr wrapper 在接下来的部分,我会代替你的composablepartial ,取出_ -suffixes,并修复foldr包装


Composable functions 可组合功能

 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 ); 


Programmatic solution 程序化解决方案

 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 ); 


A more serious task 一个更严肃的任务

 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 power ! 偏电!

partial can actually be applied as many times as needed 实际上可以根据需要多次应用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 

This makes it an indispensable tool for working with variadic functions, too 这也使它成为处理可变函数的必不可少的工具

 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 

Lastly, a demonstration of partialRight 最后,展示了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 


Summary 摘要

OK, so partial is pretty much a drop in replacement for composable but also tackles some additional corner cases. 好的, partial几乎替代了composable替代品,但是还解决了一些其他的极端情况。 Let's see how this bangs up against your initial list 让我们看看这与您的初始清单有何关系

  1. aesthetic concerns: avoids f (x) (y) (z) 美学考虑:避免f (x) (y) (z)
  2. performance: unsure, but i suspect performance is about the same 性能:不确定,但我怀疑性能大致相同
  3. debugging: still an issue because partial creates new functions 调试:仍然存在问题,因为partial创建了新功能
  4. readability: i think readability here is pretty good, actually. 可读性:实际上,我认为这里的可读性很好。 partial is flexible enough to remove points in many cases 在很多情况下, partial非常灵活,可以删除点

I agree with you that there's no replacement for fully curried functions. 我同意您的看法,完全替代了咖喱函数。 I personally found it easy to adopt the new style once I stopped being judgmental about the "ugliness" of the syntax – it's just different and people don't like different. 我个人发现,一旦我不再对语法的“丑陋性”做出判断,就很容易采用这种新样式–只是有所不同,而且人们不喜欢不同。

The current prevailing approach provides that each multi argument function is wrapped in a dynamic curry function. 当前流行的方法是将每个多参数函数包装在动态库里函数中。 While this helps with concern #1, it leaves the remaining ones untouched. 尽管这有助于解决问题#1,但其余部分保持不变。 Here is an alternative approach. 这是另一种方法。

Composable functions 可组合功能

A composable function is curried only in its last argument. 可组合函数仅在其最后一个参数中使用。 To distinguish them from normal multi argument functions, I name them with a trailing underscore (naming is hard). 为了将它们与普通的多参数函数区分开来,我用一个下划线来命名它们(命名很困难)。

 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 ); 

With the exception of drop , dropAndSum consists exclusively of multi argument or composable functions and yet we've achieved the same expressiveness as with fully curried functions - at least with this example. 除了dropdropAndSum仅由多参数或可组合函数组成,但是我们已经实现了与完全咖喱函数相同的表现力-至少在此示例中如此。

You can see that each composable function expects either uncurried or other composable functions as arguments. 您可以看到每个可组合函数都将未固化或其他可组合函数用作参数。 This will increase speed especially for iterative function applications. 这将提高速度,尤其是对于迭代功能应用程序。 However, this is also restrictive as soon as the result of a composable function is a function again. 但是,一旦可组合函数的结果再次为函数,这也将受到限制。 Look into the countWhere example below for more information. 查看下面的countWhere示例以获取更多信息。

Programmatic solution 程序化解决方案

Instead of defining composable functions manually we can easily implement a programmatic solution: 无需手动定义可组合函数,我们可以轻松地实现编程解决方案:

 // 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 ); 

Operator functions vs. higher order functions 操作员功能与高阶功能

Maybe you noticed that curry (form the first example) swaps arguments, while composable does not. 也许您注意到curry (形成第一个示例)交换了参数,而composable却没有。 curry is meant to be applied to operator functions like drop or sub only, which have a different argument order in curried and uncurried form respectively. curry仅适用于运算符函数,例如dropsub ,它们分别以curried和uncurried形式具有不同的参数顺序。 An operator function is any function that expects only non-functional arguments. 运算符函数是仅需要非函数参数的任何函数。 In this sence... 从这个意义上说...

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

Higher order functions (HOFs) don't need swapped arguments but you will regularly encounter them with arities higher than two, hence the composbale function is useful. 高阶函数(HOF)不需要交换参数,但是您经常会遇到Ar两个以上的参数,因此composbale函数非常有用。

HOFs are one of the most awesome tools in functional programming. HOF是函数式编程中最出色的工具之一。 They abstract from function application. 他们从功能应用程序中抽象出来。 This is the reason why we use them all the time. 这就是我们一直使用它们的原因。

A more serious task 一个更严肃的任务

We can solve more complex tasks as well: 我们还可以解决更复杂的任务:

 // 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 ); 

Please note that in line A we were forced to pass f explicitly. 请注意,在A行中A我们被迫显式传递f This is one of the drawbacks of composable against curried functions: Sometimes we need to pass the data explicitly. 这是可组合的针对咖喱函数的缺点之一:有时我们需要显式传递数据。 However, if you dislike point-free style, this is actually an advantage. 但是,如果您不喜欢无点样式,这实际上是一个优势。

Conclusion 结论

Making functions composable mitigates the following concerns: 使功能可组合可减轻以下担忧:

  1. aesthetic concerns (less frequent use of the curry pattern f(x) (y) (z) 美学考虑(较少使用咖喱图案f(x) (y) (z)
  2. performance penalties (far fewer function calls) 性能损失(更少的函数调用)

However, point #4 (readability) is only slightly improved (less point-free style) and point #3 (debugging) not at all. 但是,第4点(可读性)仅稍有改善(较少的无点样式),而第3点(调试)则根本没有。

While I am convinced that a fully curried approach is superior to the one presented here, I think composable higher order functions are worth thinking about. 尽管我确信完全咖喱化的方法优于此处介绍的方法,但我认为值得考虑的是可组合的高阶函数。 Just use them as long as you or your coworkers don't feel comfortable with proper currying. 只要您或您的同事对适当的欺骗感到不舒服,就使用它们。

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

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