[英]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开发人员拒绝了该技术:
f(x) (y) (z)
对典型咖喱图案的美学关注: f(x) (y) (z)
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 在接下来的部分,我会代替你的composable
与partial
,取出_
-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 让我们看看这与您的初始清单有何关系
f (x) (y) (z)
美学考虑:避免f (x) (y) (z)
partial
creates new functions 调试:仍然存在问题,因为partial
创建了新功能 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. 这是另一种方法。
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. 除了drop
, dropAndSum
仅由多参数或可组合函数组成,但是我们已经实现了与完全咖喱函数相同的表现力-至少在此示例中如此。
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
示例以获取更多信息。
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 );
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
仅适用于运算符函数,例如drop
或sub
,它们分别以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. 这就是我们一直使用它们的原因。
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. 但是,如果您不喜欢无点样式,这实际上是一个优势。
Making functions composable mitigates the following concerns: 使功能可组合可减轻以下担忧:
f(x) (y) (z)
美学考虑(较少使用咖喱图案f(x) (y) (z)
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.