部分應用和 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 演算的更多閱讀,請試試這個 。