2016-05-08 51 views
6

我相信我在數學上理解Y-組合器的想法:它返回給定功能F的固定點,因此f = Y(F)其中f滿足f == F(f)Y-combinator如何以編程方式計算固定點?

但我不明白它是如何明智的實際計算程序?

讓我們給here(我還包括在評論一個CoffeeScript的版本,它更易於閱讀)的JavaScript例如:

var Y = function (F) { 
    return (function(x) { 
    return F(function(y) { 
     return x(x)(y); 
    }); 
    })(function(x) { 
    return F(function(y) { 
     return x(x)(y); 
    }); 
    }); 
}; 


var Factorial = function (factorial) { 
    return function (n) { 
    if (n === 0) { 
     return 1; 
    } else { 
     return n * factorial(n - 1); 
    } 
    }  
}; 

/* Equivalent CoffeeScript: 
Y = (F) -> 
    ((x) -> F((y) -> x(x)(y)))((x) -> F((y) -> x(x)(y))) 

Factorial = (factorial) -> 
    if n == 0 then 1 else n * factorial(n-1) 
*/ 

Y(Factorial)(6) == 720 // => true 
computed_factorial = Y(Factorial) 

我不明白的部分是如何computed_factorial功能(固定點)實際上得到計算?通過追蹤Y的定義,您會發現它在x(x)零件處運行到無限遞歸,我看不到任何暗示的終止情況。但是,它奇怪地返回。誰能解釋一下?

+0

也許[這](http://stackoverflow.com/a/13759513/1048572)幫助? – Bergi

+1

無限遞歸是懶惰的。該函數並不總是被調用,它只是反覆傳入(在'factorial'的基本情況下,它不再被使用) – Bergi

+1

謝謝@Bergi我已經在你的幫助下找到了它。在'Y(F)'的時候,遞歸結構'x(x)'只是一個引用,它被傳入並作爲boundial變量'factorial'存儲在Factorial函數中,它不會被調用那一點。 –

回答

2

我打算使用ES6箭頭函數語法。由於您似乎瞭解CoffeeScript,因此您應該毫不費力地閱讀它。

這是你的Y組合

var Y = F=> (x=> F (y=> x (x) (y))) (x=> F (y=> x (x) (y))) 

我會用你的factorial功能壽的改進版本。這個使用了一個累加器,它會阻止評估變成一個大金字塔。此功能的過程將爲線性迭代,而您的將是遞歸。當ES6最終獲得尾部呼叫消除時,這會造成更大的差異。

語法上的差異是名義上的。因爲你只是想看看Y是如何評估的,所以它無所謂。

var factorial = Y (fact=> acc=> n=> 
    n < 2 ? acc : fact (acc*n) (n-1) 
) (1); 

那麼這將已經導致計算機開始做一些工作。因此,讓我們評估這一點,我們再往前走之前...

我希望你在你的文本編輯器中有一個非常好的支架熒光筆......

var factorial 
= Y (f=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (1)                                        // sub Y 
= (F=> (x=> F (y=> x (x) (y))) (x=> F (y=> x (x) (y)))) (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (1)                           // apply F=> to fact=> 
= (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (1)                // apply x=> to x=> 
= (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1) // apply fact=> to y=> 
= (acc=> n=> n < 2 ? acc : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (acc*n) (n-1)) (1)    // apply acc=> to 1 
= n=> n < 2 ? 1 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1*n) (n-1)        // return value 
= [Function] (n=> ...) 

所以,你可以在這裏看到,經過我們呼籲:

var factorial = Y(fact=> acc=> n=> ...) (1); 
//=> [Function] (n=> ...) 

我們得到的是在等待一個單輸入,n功能回來。現在運行 階乘現在

在我們開始之前,你可以驗證(你應該)是這裏的每專線是通過複製/粘貼是否正確在JavaScript REPL。每一行都會返回24(這是factorial(4)的正確答案,對不起,如果我寵壞了你)。這就像當你簡化分數,求解代數方程或平衡化學公式一樣;每一步都應該是一個正確的答案。

請確保將我的評論一直滾動到右側。我告訴你我在每一行上完成的操作。完成的操作的結果在隨後的行中。

並確保你有一個支架熒光筆再次派上用場......

factorial (4)                                                      // sub factorial 
= (n=> n < 2 ? 1 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1*n) (n-1)) (4)         // apply n=> to 4 
= 4 < 2 ? 1 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1*4) (4-1)           // 4 < 2 
= false ? 1 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1*4) (4-1)           // ?: 
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1*4) (4-1)              // 1*4 
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4) (4-1)               // 4-1 
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4) (3)               // apply y=> to 4 
= (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (4) (3)                  // apply x=> to x=> 
= (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4) (3)  // apply fact=> to y=> 
= (acc=> n=> n < 2 ? acc : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (acc*n) (n-1)) (4) (3)     // apply acc=> to 4 
= (n=> n < 2 ? 4 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4*n) (n-1)) (3)         // apply n=> to 3 
= 3 < 2 ? 4 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4*3) (3-1)           // 3 < 2 
= false ? 4 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4*3) (3-1)           // ?: 
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4*3) (3-1)              // 4*2 
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12) (3-1)              // 3-1 
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12) (2)               // apply y=> to 12 
= (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (12) (2)                 // apply x=> to y=> 
= (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12) (2)  // apply fact=> to y=> 
= (acc=> n=> n < 2 ? acc : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (acc*n) (n-1)) (12) (2)     // apply acc=> 12 
= (n=> n < 2 ? 12 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12*n) (n-1)) (2)        // apply n=> 2 
= 2 < 2 ? 12 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12*2) (2-1)           // 2 < 2 
= false ? 12 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12*2) (2-1)           // ?: 
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12*2) (2-1)              // 12*2 
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24) (2-1)              // 2-1 
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24) (1)               // apply y=> to 24 
= (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (24) (1)                 // apply x=> to x=> 
= (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24) (1)  // apply fact=> to y=> 
= (acc=> n=> n < 2 ? acc : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (acc*n) (n-1)) (24) (1)     // apply acc=> to 24 
= (n=> n < 2 ? 24 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24*n) (n-1)) (1)        // apply n=> to 1 
= 1 < 2 ? 24 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24*1) (1-1)           // 1 < 2 
= true ? 24 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24*1) (1-1)           // ?: 
= 24 

我見過的Y其他實現也是如此。這是一個簡單的過程,從頭開始構建另一個(用於JavaScript)。

// text book 
var Y = f=> f (Y (f)) 

// prevent immediate recursion (javascript is applicative order) 
var Y = f=> f (x=> Y (f) (x)) 

// remove recursion using U combinator 
var Y = U (h=> f=> f (x=> h (h) (f) (x))) 

// given: U = f=> f (f) 
var Y = (h=> f=> f (x=> h (h) (f) (x))) (h=> f=> f (x=> h (h) (f) (x))) 
1

在一個懶惰的評價語言,在Y組合子可以定義爲:

Y = (f => 
    (x => f(x(x))) 
    (x => f(x(x)))) 

但由於JavaScript是一個熱切的評價語言,定義ÿ這樣會導致X( x)零件在您嘗試將Y應用於函數時無限期地遞歸。

爲了解決這個問題,可以引入一個匿名的「包裝器」函數來延遲執行x。這個包裝函數在調用時的行爲與x(x)相同,但會立即返回,因爲它只是一個函數定義。

明知X(X)將被綁定到遞歸函數,在本例的情況下:

Factorial = f => n => n==0 ? 1 : n*f(n-1) 

我們可以提前告訴,只有一個參數將被傳遞給它。它允許我們使用以下方式生成的行爲與任何給定函數F(X)的匿名函數

f => x => f(x) 

當我們將這種模式向X(X)來看, Ÿ將不再無限期重複併成爲:

Y = (f => 
    (x => f(y => x(x)(y))) 
    (x => f(y => x(x)(y)))) 
0

的Y組合是在演算中最有趣的現象之一。我懷疑馬上看到它,可以想出它的功能解釋。

Y = f => (g => g(g))(g => n => f(g(g))(n)); 

讓我們試着瞭解它的一步一步推導。我將使用箭頭功能,所以如果你不熟悉它們,請按照this link。他們非常簡單。 x => x表示function(x){return x;}。 JS this關鍵字在箭頭內部具有不同的含義,但這是本主題的主題。

因此,我們總是會用階乘函數,但我們推導出的Y組合函數對於所有遞歸函數都是有效的。

階乘函數可以簡單地表述如下

var fa = n => n === 0 ? 1 : n*fa(n-1); 
fact(5) // <- 120 

但是說我們不想指fa遞歸函數;相反,我們想從一個假設版本的因子函數中推導一個工作階乘函數。什麼是假設的因子函數?假設的階乘函數具有適當的階乘函數並返回工作階乘函數。像下面

var fh = f => n => n === 0 ? 1 : n*f(n-1); 

所以,如果我通過fa功能fh作爲參數應當編制。喜歡;

fh(fa)(5); // <- 120 

但我們不希望參考另一個階乘函數像fa因爲我們已經「之類的」的fh函數中定義的階乘邏輯。然後我們想。 fh保持f自變量處於關閉狀態,並返回工作階乘函數(n => n === 0 ? 1 : n*f(n-1)),如果我通過了像fa這樣的適當因子函數。那麼,如果我把它傳遞給它呢?快點試試fh(fh)(5) // <- NaN吧..!

所以我們開始玩內部功能。通常我會通過這一步,但看到轉換可能會有幫助......所以讓我們繼續。我可以定義fb函數返回我一個函數,它有兩個參數,本身階乘數n

fb = (f,n) => n === 0 ? 1 : n* f(f,n-1), // fb(fb,5) <- 120 

到目前爲止好,但兩個參數階乘函數是沒有地方接近我所期待的。我可以通過添加另一個功能步驟來分開它們。

fc = f => n => n === 0 ? 1 : n* f(f)(n-1), // fc(fc)(5) <- 120 

現在這是非常接近我們的假設功能fh。但內部顯示f(f)(n-1)我們必須糾正這個顯示f(n-1)。那麼可能我們可以用JS美容IIFE來幫我們...

fd => f => n => ((g,n) => n === 0 ? 1 : n * (g,n-1))(f(f),n) // fd(fd)(5) <- 120 

你能看到IIFE ..嗎? ((g,n) => n === 0 ? 1 : n * g(n-1))(f(f),n)然而,雖然這似乎很好,但我必須擺脫雙重論點(g,n) IIFE才能達到預期的效果。這將進一步發揮功能。

fe => f => n => (g => n => n === 0 ? 1 : n * g(n-1))(f(f))(n) // fe(fe)(5) <- 120 

現在我們有內部g => n => n === 0 ? 1 : n * g(n-1)這是我們假設的功能fh的身體。這意味着我可以替代(我喜歡這個部分..就像微積分替代;實際上它是......)fh在上面的函數中看起來像;

fe => f => n => fh(f(f))(n) // fe(fe)(5) <- 120 

好的時候把它包起來。我首先想要的是什麼..?我想喂fh到函數(Y-combinator)並得到它的固定點。在這裏,我知道fe(fe)使用fh並返回正確工作階乘函數。因此,讓我們定義一個函數,將我們的語言遞歸函數作爲參數,並給我們一些工作(固定)。 IIFE再次提供幫助。

Y = f => (g => g(g))(g => n => f(g(g))(n)); 

所以這應該適用於任何事情。讓我們用一個假設的斐波那契函數來嘗試我們的Y組合器。

var Y = f => (g => g(g))(g => n => f(g(g))(n)), 
 
fibH = f => n => n === 0 ? 0 
 
          : n === 1 ? 1 
 
            : f(n-2) + f(n-1), 
 
fibo = Y(fibH); 
 
console.log(fibo(10));

我希望這一切都清楚......

相關問題