JavaScript中的函数组合

JavaScript中的函数组合,javascript,functional-programming,composition,Javascript,Functional Programming,Composition,我知道这是很有可能的,因为我的Haskell朋友们似乎能够在他们的睡眠中做这类事情,但我不能用JS来处理更复杂的函数组合 比如说,您有以下三个功能: const round = v => Math.round(v); const clamp = v => v < 1.3 ? 1.3 : v; const getScore = (iteration, factor) => iteration < 2 ? 1 : iteration === 2 ?

我知道这是很有可能的,因为我的Haskell朋友们似乎能够在他们的睡眠中做这类事情,但我不能用JS来处理更复杂的函数组合

比如说,您有以下三个功能:

const round = v => Math.round(v);

const clamp = v => v < 1.3 ? 1.3 : v;

const getScore = (iteration, factor) =>
    iteration < 2 ? 1 :
    iteration === 2 ? 6 :
    (getScore(iteration - 1, factor) * factor);
执行此操作的代码可能如下所示:

const getRoundedClampedScore = compose(round, clamp, getScore);
var saferGetScore = R.converge(getScore, [round, clamp]);

但是compose函数是什么样子的呢?如何调用
getRoundedClampedScore
?或者这是一个可怕的错误?

函数应该首先使用要合成的核心函数,使用rest参数将其他函数放入一个数组,然后返回一个函数,该函数使用
i
th参数调用数组中的
i
th函数:

constround=v=>Math.round(v);
常数钳=v=>v<1.3?1.3:v;
const getScore=迭代=>因子=>
迭代<2?1 :
迭代===2?6 :
(得分(迭代-1)(因子)*因子);
const compose=(fn,…transformArgsFns)=>(…args)=>{
const newArgs=transformArgsFns.map((tranformArgFn,i)=>tranformArgFn(args[i]);
返回fn(…新参数);
}
const getRoundedClampedScore=组合(getScore,round,clampedscore);
控制台日志(getRoundedClampedScore(1)(5))
控制台日志(getRoundedClampedScore(3.3)(5))

log(getRoundedClampedScore(3.3)(1))
我认为您遇到的部分问题是,
compose
实际上不是您要寻找的函数,而是其他函数
compose
通过一系列函数提供值,而您希望预处理一系列参数,然后将处理后的参数提供给最终函数

有一个非常适合此功能的实用程序函数,称为
converge
。converge所做的是生成一个函数,该函数将一系列函数应用于一对一对应关系上的一系列参数,然后将所有这些转换后的参数馈送到另一个函数中。在您的情况下,使用它将如下所示:

const getRoundedClampedScore = compose(round, clamp, getScore);
var saferGetScore = R.converge(getScore, [round, clamp]);
如果您不想仅仅为了使用这个
converge
函数而卷入整个第三方库,您可以用一行代码轻松地定义您的。这看起来很像队长在回答中使用的,但是少了一个
(你绝对不应该把它命名为
compose
,因为这是一个完全不同的概念):


Haskell程序员通常可以简化表达式,就像简化数学表达式一样。我将在这个答案中向您展示如何做到这一点。首先,让我们看看表达式的构建块:

round::Number->Number
夹具::编号->编号
getScore::编号->编号->编号
通过组合这三个函数,我们希望创建以下函数:

getRoundedClampedScore::Number->Number->Number
getRoundedClampedScore迭代因子=getScore(圆形迭代)(钳制因子)
我们可以将此表达式简化如下:

getRoundedClampedScore迭代因子=getScore(圆形迭代)(钳制因子)
getRoundedClampedScore迭代=getScore(圆形迭代)。夹紧
getRoundedClampedScore迭代=(getScore.round)迭代。夹紧
getRoundedClampedScore迭代=(.clamp)((getScore.round)迭代)
getRoundedClampedScore=(.clamp)。(getScore.round)
getRoundedClampedScore=(.clamp)。得分。圆形的
如果要将其直接转换为JavaScript,则可以使用反向函数组合:

const pipe = f => g => x => g(f(x));

const compose2 = (f, g, h) => pipe(g)(pipe(f)(pipe(h)));

const getRoundedClampedScore = compose2(getScore, round, clamp);

// You'd call it as follows:

getRoundedClampedScore(iteration)(factor);

也就是说,最好的解决方案是简单地以有意义的形式定义它:

const compose2=(f,g,h)=>x=>y=>f(g(x))(h(y));
const getRoundedClampedScore=compose2(getScore,round,climp);

通常是有用的,但有时是毫无意义的。

通常,一个
compose
函数通过提供的函数按与提供的顺序相反的顺序提供数据。因此,在
compose(getScore,round,clamp)
的情况下,人们希望执行
clamp
,然后
round
,然后
getScore
。通过函数从左到右传递数据的实用函数通常称为
pipe
。现在我更仔细地看一下您的代码,这里的实现与
compose
完全不同。它将所有函数(第一个函数除外)应用于每个参数,然后将这些结果输入到第一个函数中。这不是
compose
函数所做的。更简单的是:
const compose=(f,…fs)=>(…xs)=>f(…fs.map((f,i)=>f(xs[i])compose()
的精确定义,而是更笼统地询问了有关composition的问题。那太草率了。我喜欢这里的函数签名及其简单性(尤其是@AaditMShah的速记),因此这可能是我最终将使用的代码。我也非常尊重@JLRishe对上述异议的评论。我真的很感谢你们花了这么多时间在这件事上,我真的想把答案奖励给你们三个人。我认为“撰写函数看起来像什么”是一个错误的问题。只有一个
compose
函数,人们不应该发明新的
compose
函数来适应特定的情况。@JLRishe我不同意。正常的函数组合(即
(b->c)->(a->b)->a->c)就是函数组合的最基本形式。还有更复杂的函数组合形式,如
(c->d)->(a->b->c)->a->b->d
。在这个问题中,函数组合的形式是
(a->b->c)->(d->a)->(e->b)->d->e->c
。现在,更复杂的形式是o