为什么在Javascript中函数组合从右到左进行?

8

函数组合是从右往左组合的:

const comp  = f => g => x => f(g(x));
const inc = x => x + 1;
const dec = x => x - 1;
const sqr = x => x * x;
let seq = comp(dec)(comp(sqr)(inc));

seq(2); // 8

seq(2)被转换为dec(sqr(inc(2))),应用程序的顺序是inc(2)...sqr...dec。因此,函数被调用的顺序与它们传递给comp的顺序相反。对于JavaScript程序员来说,这并不直观,因为他们习惯于方法链,该链从左到右:

o = {
  x: 2,
  inc() { return this.x + 1, this },
  dec() { return this.x - 1, this },
  sqr() { return this.x * this.x, this }
}

o.dec().sqr().inc(); // 2

我认为这很令人困惑。以下是一种反向构图:
const flipped = f => g => x => g(f(x));
let seql = flipped(dec)(flipped(sqr)(inc));

seql(2); // 2

有什么原因使得函数组合从右到左进行吗?

“因此,函数的评估顺序与它们应用的顺序相反。” - 呃,什么? - Bergi
@Bergi 嗯,那也改变不了 comp 从右到左求值的事实。我对此没有任何问题,只是想问为什么? - user6445533
基本上可以归结为“函数求值总是从右到左”。如果您将组合视为中缀运算符,则更加明显。 - Bergi
@Bergi 我数学很差,但是数学函数组合不是满足结合律吗,也就是说 f . (g . h) === (f . g) . h?无论如何,如果一直都是这样的,并且许多人已经习惯了,我不想质疑它。 - user6445533
哦,是的,我指的当然是结合性。你说你的 compl 函数是“左结合”的时候,实际上你交换了参数,这让我完全困惑了。 - Bergi
显示剩余5条评论
2个回答

9
回答原始问题:为什么函数组合从右到左组合?
  1. 因为在数学中传统上是这样做的
  2. comp(f)(g)(x) 的顺序与 f(g(x)) 相同
  3. 可以轻松创建反向或正向组合(请参见示例)

正向函数组合:

const comp = f => g => x => f(g(x));
const flip = f => x => y => f(y)(x);
const flipped = flip(comp);

const inc = a => a + 1;
const sqr = b => b * b;

   comp(sqr)(inc)(2); // 9, since 2 is first put into inc then sqr
flipped(sqr)(inc)(2); // 5, since 2 is first put into sqr then inc

这种调用函数的方式被称为“柯里化”,它的工作原理如下:
// the original:
comp(sqr)(inc)(2); // 9

// is interpreted by JS as:
( ( ( comp(sqr) ) (inc) ) (2) ); // 9 still (yes, this actually executes!)

// it is even clearer when we separate it into discrete steps:
const compSqr = comp(sqr); // g => x => sqr(g(x))
compSqr(inc)(2);   // 9 still
const compSqrInc = compSqr(inc); // x => sqr(x + 1)
compSqrInc(2);     // 9 still
const compSqrInc2 = compSqrInc(2); // sqr(3)
compSqrInc2;       // 9 still

因此,函数是由JS解释器从左到右组合和解释的,而在执行时,它们的值从右到左通过每个函数流动。简而言之:先从外到内,再从内到外。

但是flip有一个限制,即翻转组合不能与自身组合形成“高阶组合”:

const comp2 = comp(comp)(comp);
const flipped2 = flipped(flipped)(flipped);
const add = x => y => x + y;

   comp2(sqr)(add)(2)(3); // 25
flipped2(sqr)(add)(2)(3); // "x => f(g(x))3" which is nonsense

结论:从右到左的顺序是传统的/惯例的,但不直观。


3
你的问题实际上是关于函数组合运算符定义中参数顺序的问题,而不是右结合性或左结合性。在数学上,我们通常写成“f o g”(相当于你定义中的comp(f)(g)),表示将x映射为f(g(x))的函数。因此,“f o (g o h)”和“(f o g) o h”是等价的,并且都表示将每个参数x映射为f(g(h(x)))的函数。
也就是说,我们有时会写成f;g(相当于你代码中的compl(f)(g)),表示将x映射为g(f(x))的函数。因此,(f;g);h和f;(g;h)都表示将x映射为h(g(f(x)))的函数。
参考文献:https://en.wikipedia.org/wiki/Function_composition#Alternative_notations

好的,我把可交换律和结合律搞混了。谢谢! - user6445533
1
关于备选符号(";"):我们也发现了 "pipe" 运算符,例如 >>>|>,它们更好地可视化了“从左到右”的概念,在我看来。 - phipsgabler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接