如何将函数转换为自由点形式?

Dre*_*eur 3 functional-programming pointfree

假设我有一个JavaScript函数

function f(x) {
  return a(b(x), c(x));
}
Run Code Online (Sandbox Code Playgroud)

我如何将其转换为无点函数?通过编写功能?还有资源获取更多信息吗?

kqr*_*kqr 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);
Run Code Online (Sandbox Code Playgroud)

有趣的是,定义这三个功能.

咖喱

通常,当您在JavaScript中定义多个参数的函数时,您可以将它们定义为

function f(x, y) {
    // body
}
Run Code Online (Sandbox Code Playgroud)

你通过做类似的事情来称呼他们f(3, 4).这就是函数式编程中所谓的"无故障函数".您也可以想象定义函数

function f(x) {
    return function(y) {
        //body
    }
}
Run Code Online (Sandbox Code Playgroud)

这些功能称为" curried函数".(顺便说一句,它们是以一位名叫库里的数学家的名字命名的,如果你想知道这个奇怪的名字的话.)咖喱函数反而被称为

f(3)(4)
Run Code Online (Sandbox Code Playgroud)

但除此之外,这两个函数的行为非常相似.一个区别是,当功能被咖喱时,使用无点样式更容易.我们的curry函数只需要像第一个函数一样使用一个未经验证的函数,并将其转换为像第二个函数一样的curried函数.curry可以定义为

function curry(f) {
    return function(a) {
        return function(b) {
            return f(a, b);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用它.pow(3, 4)你可以做到,而不是做到81

cpow = curry(pow);
cpow(3)(4);
Run Code Online (Sandbox Code Playgroud)

cpow是咖喱的版本pow.它不会同时采用两个参数 - 它需要单独使用它们.在您的具体情况下,这允许我们来自

function f(x) {
    return a(b(x), c(x));
}
Run Code Online (Sandbox Code Playgroud)

function f(x) {
    return curry(a)(b(x))(c(x));
}
Run Code Online (Sandbox Code Playgroud)

这是进步!(虽然我承认它在JavaScript中看起来很奇怪......)现在,对于不那么辛辣的牧场.

FMAP

第二个难题是fmap(f, g),它将两个函数作为参数并组成它们.我的意思是,

fmap(f, g)(x) == f(g(x))
Run Code Online (Sandbox Code Playgroud)

这很容易定义,我们只是让

function fmap(f, g) {
    return function(x) {
        return f(g(x));
    }
}
Run Code Online (Sandbox Code Playgroud)

当您想要按顺序执行两项操作时,这非常有用.假设你想做无用的操作log(exp(x)).你可以用传统方式做到这一点:

function logexp(x) {
    return log(exp(x));
}
Run Code Online (Sandbox Code Playgroud)

你可以改为做

logexp = fmap(log, exp);
Run Code Online (Sandbox Code Playgroud)

这通常称为组成两个功能.要将此连接到您的示例,最后我们将其关闭,我们已将其重构为

function f(x) {
    return curry(a)(b(x))(c(x));
}
Run Code Online (Sandbox Code Playgroud)

我们现在注意到它与它的功能体之间有一些视觉上的相似性fmap.让我们改写它,fmap它变成了

function f(x) {
    return fmap(curry(a), b)(x)(c(x));
}
Run Code Online (Sandbox Code Playgroud)

(看看我是如何到达那里,想象f = curry(a)g = b,用最后一点c(x)不会改变.)

美联社

我们的最后一个拼图是ap(f, g),它有两个函数和一个参数,并且做了一件奇怪的事情.我甚至不会尝试解释它,所以我只会告诉你它的作用:

ap(f, g)(x) == f(x)(g(x))
Run Code Online (Sandbox Code Playgroud)

请记住,这f实际上只是两个参数的函数,只有我们写一点不同才能做出魔法.ap在JavaScript中定义为

function ap(f, g) {
    return function(x) {
        return f(x)(g(x));
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,将其置于更实际的背景中:假设您想要将数字提升到其自身的平方根.你可以做到

function powsqrt(x) {
    return pow(x, sqrt(x));
}
Run Code Online (Sandbox Code Playgroud)

或者,通过你对第一部分关于currying的新发现的知识ap和记忆cpow,你也可以这样做

powsqrt = ap(cpow, sqrt);
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为cpow是咖喱版pow.您可以自己验证,当ap扩展定义时,这成为正确的事情.

现在,为了将所有这些与你的例子联系在一起,我们需要转向

function f(x) {
    return fmap(curry(a), b)(x)(c(x));
}
Run Code Online (Sandbox Code Playgroud)

进入决赛,完全无点版本.如果我们看一下定义ap,我们就会看到我们可以在这里做些什么来把它变成无点版本!

function f(x) {
    return ap(fmap(curry(a), b), c)(x);
}
Run Code Online (Sandbox Code Playgroud)

基本上,理解这一点的最简单方法是现在"展开"调用ap.将调用替换为ap函数体!那么,我们通过仅代替,得到的是

function f(x) {
    return function(y) {
        return fmap(curry(a), b)(y)(c(y));
    }(x);
}
Run Code Online (Sandbox Code Playgroud)

我已经改名为一个xy避免名称冲突.这仍然有点奇怪,但我们可以缩短一点.毕竟,它是一样的

function f(x) {
    return fmap(curry(a), b)(x)(c(x));
}
Run Code Online (Sandbox Code Playgroud)

这是我们开始的!我们的呼吁ap是正确的.如果你愿意,你可以进一步展开这一点,看看在完成所有事情之后,我们实际上最终得到了我们开始的事情.我把它留作练习.

包起来

无论如何,你的代码的最后一次重构使它成为了

function f(x) {
    return ap(fmap(curry(a), b), c)(x);
}
Run Code Online (Sandbox Code Playgroud)

这当然是一样的

f = ap(fmap(curry(a), b), c);
Run Code Online (Sandbox Code Playgroud)

就是这样!