部分应用和 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 演算的更多阅读,请试试这个 。