假设我有一个JavaScript函数
function f(x) {
return a(b(x), c(x));
}
我如何将其转换为无点函数?通过编写功能?还有资源可以获得更多信息吗?
答案 0 :(得分:5)
通常,当您将函数转换为无点样式时,没有简单的规则可循。要么你必须猜测,要么你可以自动化它。在Haskell IRC频道中,我们有lambdabot,它非常适合将Haskell函数转换为无点样式。我通常只是咨询一下,然后如果我需要知道它是如何工作的话,我会向后工作。
您可以使用一些有用的功能来解决您的特定示例。我将在下面向您展示它是如何工作的,但要注意它可能需要大量的游戏才能理解。如果你知道真正基本的lambda演算,它也会有所帮助,因为JavaScript语法有时会妨碍你。
无论如何,这里是:
基本上,要正确执行此操作,您需要三个功能:fmap(f, g)
,ap(f, g)
和curry(f)
。如果你有这些,f(x)
很容易被定义为(并且在例如Haskell中看起来更整洁)
f = ap(fmap(curry(a), b), c);
有趣的是定义这三个函数。
通常,在JavaScript中定义多个参数的函数时,可以将它们定义为
function f(x, y) {
// body
}
你通过做f(3, 4)
之类的事情来打电话给他们。这就是函数式编程中所谓的“ uncurried 函数”。你也可以想象定义像
function f(x) {
return function(y) {
//body
}
}
这些功能称为“ curried 功能”。 (顺便说一句,它们是以一个名叫库里的数学家的名字命名的,如果你想知道这个奇怪的名字的话。)相反,通过做法来调用Curried函数
f(3)(4)
但除此之外,这两个函数的行为非常相似。一个区别是,当功能被咖喱时,使用无点样式更容易。我们的curry
函数只需要像第一个函数一样使用一个未经验证的函数,并将其转换为像第二个函数一样的curried函数。 curry
可以定义为
function curry(f) {
return function(a) {
return function(b) {
return f(a, b);
}
}
}
现在,你可以使用它。而不是pow(3, 4)
获得81,你可以做
cpow = curry(pow);
cpow(3)(4);
cpow
是pow
的curried版本。它不会同时采用两个参数 - 它需要单独使用它们。在您的具体情况下,这允许我们从
function f(x) {
return a(b(x), c(x));
}
到
function f(x) {
return curry(a)(b(x))(c(x));
}
这是进步! (虽然我承认它在JavaScript中看起来很奇怪......)现在,对于不那么辛辣的牧场。
第二部分拼图是fmap(f, g)
,它将两个函数作为参数并组成它们。我所说的是,
fmap(f, g)(x) == f(g(x))
这很容易定义,我们只是让
function fmap(f, g) {
return function(x) {
return f(g(x));
}
}
当您想要按顺序执行两项操作时,这非常有用。假设你想做无用的操作log(exp(x))
。你可以用传统方式做到这一点:
function logexp(x) {
return log(exp(x));
}
你可以改为
logexp = fmap(log, exp);
这通常称为撰写两个函数。要将此连接到您的示例,最后我们将其关闭,我们已将其重构为
function f(x) {
return curry(a)(b(x))(c(x));
}
我们现在注意到它与fmap
的函数体之间有一些视觉上的相似性。让我们用fmap
重写它,它就变成了
function f(x) {
return fmap(curry(a), b)(x)(c(x));
}
(看看我如何到达那里,想象f = curry(a)
和g = b
。c(x)
的最后一位不会改变。)
我们的最后一个拼图是ap(f, g)
,它有两个函数和一个参数,并且做了一件奇怪的事情。我甚至不会尝试解释它,所以我只会告诉你它的作用:
ap(f, g)(x) == f(x)(g(x))
请记住,f
实际上只是两个参数的函数,只有我们写一点不同才能做出魔法。 ap
在JavaScript中定义为
function ap(f, g) {
return function(x) {
return f(x)(g(x));
}
}
所以,把它放在一个更实际的背景中:假设你想要将数字提高到它自己的平方根。你可以做到
function powsqrt(x) {
return pow(x, sqrt(x));
}
或,凭借您对ap
的新发现的知识,并在第一部分中记住cpow
关于currying,你也可以这样做
powsqrt = ap(cpow, sqrt);
这是有效的,因为cpow
是pow
的咖喱版本。当ap
的定义被扩展时,您可以自己验证这是正确的。
现在,要将所有这些与您的示例结合在一起,我们需要转向
function f(x) {
return fmap(curry(a), b)(x)(c(x));
}
进入最终,完全无点的版本。如果我们查看ap
的定义,我们会发现我们可以在此处做一些事情,将其变为无点版本!
function f(x) {
return ap(fmap(curry(a), b), c)(x);
}
基本上,理解这一点的最简单方法是“展开”对ap
的调用。将ap
的调用替换为函数体!那么,我们通过仅代替来得到的是
function f(x) {
return function(y) {
return fmap(curry(a), b)(y)(c(y));
}(x);
}
我已将一个x
重命名为y
以避免名称冲突。这仍然有点奇怪,但我们可以缩短一点。毕竟,它与
function f(x) {
return fmap(curry(a), b)(x)(c(x));
}
这是我们开始的!我们对ap
的呼吁是正确的。如果你愿意,你可以进一步展开这一点,看看在完成所有事情之后,我们实际上最终得到了我们开始的事情。我把它留作练习。
无论如何,您的代码的最后一次重构使其成为
function f(x) {
return ap(fmap(curry(a), b), c)(x);
}
当然与
相同f = ap(fmap(curry(a), b), c);
就是这样!