部分应用和 Currying

从技术上讲,Ruby 没有函数,而是方法。但是,Ruby 方法的行为几乎与其他语言中的函数相同:

def double(n)
  n * 2
end

这个正常的方法/函数接受参数 n,将其加倍并返回值。现在让我们定义一个更高阶的函数(或方法):

def triple(n)
  lambda {3 * n}
end

triple 返回一个方法,而不是返回一个数字。你可以使用 Interactive Ruby Shell 对其进行测试 :

$ irb --simple-prompt
>> def double(n)
>>   n * 2
>> end
=> :double
>> def triple(n)
>>   lambda {3 * n}
>> end
=> :triple
>> double(2)
=> 4
>> triple(2)
=> #<Proc:0x007fd07f07bdc0@(irb):7 (lambda)>

如果你想真正获得三倍数,你需要调用(或减少)lambda:

triple_two = triple(2)
triple_two.call # => 6

或者更简洁:

triple(2).call

Currying 和部分应用

这在定义非常基本的功能方面没有用,但是如果你想拥有不立即调用或减少的方法/函数,它会很有用。例如,假设你要定义按特定数字添加数字的方法(例如 add_one(2) = 3)。如果你必须定义大量这些,你可以这样做:

def add_one(n)
  n + 1
end 

def add_two(n)
  n + 2
end

但是,你也可以这样做:

add = -> (a, b) { a + b }
add_one = add.curry.(1)
add_two = add.curry.(2)

使用 lambda 演算我们可以说 add(λa.(λb.(a+b)))。Currying 是一种部分应用 add 的方式。所以 add.curry.(1),是 (λa.(λb.(a+b)))(1),可以减少到 (λb.(1+b))。部分应用意味着我们将一个参数传递给 add 但是留下了另一个参数以供稍后提供。输出是一种专门的方法。

更有用的 currying 例子

假设我们有很大的通用公式,如果我们指定某些参数,我们可以从中得到具体的公式。考虑这个公式:

f(x, y, z) = sin(x\*y)*sin(y\*z)*sin(z\*x)

这个公式是为了三维工作而制作的,但是假设我们只想要这个关于 y 和 z 的公式。我们也要说忽略 x,我们想将它的值设置为 pi / 2。我们先来制定一般公式:

f = ->(x, y, z) {Math.sin(x*y) * Math.sin(y*z) * Math.sin(z*x)}

现在,让我们使用 currying 来获得我们的 yz 公式:

f_yz = f.curry.(Math::PI/2)

然后调用存储在 f_yz 中的 lambda:

f_xy.call(some_value_x, some_value_y)

这很简单,但是我们想要得到 xz 的公式。如果不是最后一个参数,我们如何才能将 y 设置为 Math::PI/2?嗯,这有点复杂:

f_xz = -> (x,z) {f.curry.(x, Math::PI/2, z)}

在这种情况下,我们需要为我们未预先填充的参数提供占位符。为了保持一致性,我们可以像这样写 f_xy

f_xy = -> (x,y) {f.curry.(x, y, Math::PI/2)}

以下是 lambda 演算如何为 f_yz 工作:

f = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x))))
f_yz = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x)))) (π/2) # Reduce => 
f_yz = (λy.(λz.(sin((π/2)*y) * sin(y*z) * sin(z*(π/2))))

现在让我们来看看 f_xz

f = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x))))
f_xz = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x)))) (λt.t) (π/2)  # Reduce =>
f_xz = (λt.(λz.(sin(t*(π/2)) * sin((π/2)*z) * sin(z*t))))

有关 lambda 演算的更多阅读,请试试这个