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