2011-03-30 52 views
5

我幾天前在「匿名遞歸C#」中衝浪到this網站。文章的主旨是,下面的代碼將不會在C#中的工作:「匿名遞歸」在.NET中工作嗎?它在單聲道

Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n; 

文章然後進入有關如何使用currying一些細節和Y-combinator在C#回到「匿名遞歸」。這很有意思,但我擔心我的日常編碼有點複雜。在這一點上至少...

我喜歡看到自己的東西,所以我打開單聲道CSharp REPL並進入該行。沒有錯誤。所以,我輸入fib(8);。非常令我驚喜的是,它很有用! REPL回覆21

我想也許這是REPL的一些魔力,所以我點燃了'vi',輸入下面的程序,並編譯它。

using System; 

public class Program 
{ 
    public static void Main(string[] args) 
    { 
     int x = int.Parse(args[0]); 
     Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n; 
     Console.WriteLine(fib(x)); 
    } 
} 

它建成並運行得很好!

我在Mac上運行Mono 2.10。我現在無法訪問Windows計算機,所以我無法在Windows上的.NET上進行測試。

這是否已在.NET上修復,或者這是Mono的靜音功能?這篇文章已經有幾年了。

如果它只是單聲道,我不能等待下一個求職面試,他們要求我用我選擇的語言(Mono C#)編寫Fibinocci函數,但我必須提供.NET不起作用的警告。呃,其實我可以等我喜歡我的工作。不過,有趣的......

更新:

單是不是真的在做「匿名」遞歸,因爲它是用fib爲命名的委託。我的錯。在賦值之前,Mono C#編譯器假設null值爲fib,這一事實如下所述。我說「編譯器」,因爲即使.NET C#編譯器不能編譯代碼,.NET CLR也會運行生成的程序集。

對於所有采訪納粹在那裏:

Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n; 

可以用一個迭代的版本替換:

Func<int, int> fib = n => 
{ 
    int old = 1; 
    int current = 1; 
    int next; 

    for (int i = 2; i < n; i++) 
    { 
     next = current + old; 
     old = current; 
     current = next; 
    } 
    return current; 
}; 

您可能希望這樣做,因爲遞歸的版本是在像語言低效C#。有些人可能會建議使用memoization,但是,由於這種方法仍然比迭代方法慢,所以它們可能只是流浪者。 :-)

但是,在這一點上,這變成了功能性編程的廣告,而不是其他任何東西(因爲遞歸版本非常好)。這與我原來的問題確實沒有任何關係,但有些答案認爲這很重要。

+2

如果有工作面試者問我我會走出去。 – JonH 2011-03-30 14:56:38

+0

最後我檢查了你還必須在另外一行聲明'Func',我很樂意錯誤! – BrokenGlass 2011-03-30 14:57:19

+1

試過了,C#3.5失敗了。 – 2011-03-30 14:59:10

回答

7

這是單聲道編譯器中的bug。它違反了specification的第12.3.3節。變量fib不能用於變量初始值設定項中,因爲它沒有明確賦值。

+0

6年前開放......很好地看到bug在開放源代碼中比在閉源軟件中「更快」得到修復;) – 2011-03-31 17:36:52

+0

@Matthew - 從第一天開始,在任何軟件中可能會有錯誤持續到最後一個用戶終於把它關掉了。我忍不住迴應你的疑惑,儘管......至少這個bug還沒有17年左右的時間:http://techchunks.com/technology/microsoft-to-patch-17-year-old-computer-bug/ – Justin 2011-04-13 21:57:31

+1

2010年發現...這是1個月 – 2011-04-14 00:45:58

3

試試這個...

Func<int, int> fib = null; 
fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n; 

...問題是,當您嘗試在上述方法中使用它時,fib未定義,因此靜態分析器會報告編譯器錯誤。

+0

謝謝馬修。我明白了。實際上,他們在文章中給出了這個例子。 – Justin 2011-03-30 15:04:59

+0

這幾乎可以肯定REPL將你的內聯任務轉化爲什麼。基本上VS扼流圈對拉姆達的評價;在創建將分配給fib的lambda時,「fib」在技術上還不存在。您的Mono編譯器足夠聰明,可以退後一步,看到更大的聲明。 – KeithS 2011-03-30 15:33:05

+2

@KeithS在單聲道中這不是一個「足夠聰明」的東西......它是來自規範的錯誤。請參閱Eric Lippert的回覆(他應該知道......他在微軟C#團隊中。) – 2011-03-30 16:32:53

0

在Microsoft的C#編譯器中,只有在您首次將fib設置爲null時才能使用。

否則,它會給出錯誤,因爲fib在分配前會被使用。
Mono的編譯器是足夠「智能」來避免這個錯誤(換句話說,它違反了官方規格)

+11

足夠聰明,你的意思是比規範更聰明,對吧?有些人可能會說這是一個錯誤。 – 2011-03-30 14:58:48

+0

哪個編譯器「更聰明」值得商榷。他們的設計做出了不同的決定。 – 2011-03-30 14:59:23

+0

爲了保證完整性,違反規範的請參閱第12.3.3節。變量fib並不是明確賦值的,因爲它不在語句之前,它只在*語句後變成明確賦值。 – 2011-03-30 15:21:08

7

正如我在評論上面提到,如果單做的是那麼他們有一個bug。規範很明顯,這應該被視爲一個錯誤。這個錯誤當然是無害的,大部分時間都是你想要的。我們考慮改變規則以使這種遞歸合法;基本上,我們不得不在規範中增加一個特例,說這個狹義定義的情況是合法的。儘管如此,它的優先級從來沒有那麼高。

有關此問題的更多的心思看到關於這個問題我的文章:

http://blogs.msdn.com/b/ericlippert/archive/2006/08/18/706398.aspx

順便說一句,我就不會僱傭任何人誰給了我一個直線上升的遞歸實現FIB在接受媒體採訪。它是極其效率低下;其運行時間與其輸出的大小成比例,並且fib按指數規律增長。爲了使其有效地使用遞歸和記憶,或實施明顯的迭代解決方案。

+0

Eric,謝謝你的迴應。鏈接 – Justin 2011-03-30 15:39:22

+0

很顯然,面試的破解本來就是一個笑話,我希望有人能夠對效率做出一次性的評論,但我很驚訝在這裏聽到你的消息,畢竟,你對我從博客上發表的評論是「 Awesome Wes。這是我見過的y combinator最明確的解釋之一。「也許我應該回答說,我喔uld永遠不會僱用一個不能實現tail call消除的編譯器。 :-)微笑,這是早(至少對我來說)。 – Justin 2011-03-30 15:47:42

+0

@Justin:採取的一點。然而,公平的說,Wes的文章並不打算給出一個有效的fib實現,它的目的是作爲Y combinator的教程。 – 2011-03-30 15:50:20

1

看來,在我興奮的時候,我從根本上弄錯了。 .NET和Mono都沒有像原始文章那樣提供「匿名遞歸」。您無法將fib作爲獨立實體傳遞。

退房的單聲道C#REPL以下順序:

csharp> Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n; 
csharp> fibCopy = fib; 
csharp> fib(6); 
8 
csharp> fibCopy(6); 
8 
csharp> fib = n => n * 2; 
csharp> fib(6); 
12 
csharp> fibCopy(6); 
18 

這是因爲:

fib = n => n * 2; 
fibCopy = n > 1 ? fib(n - 1) + fib(n - 2) : n; 

換句話說,

fibCopy = n > 1 ? (n - 1) * 2 + (n - 2) * 2 : n; // at the moment 

顯然,fibCopy只是指向在目前的定義fib(代表),而不是本身。所以Mono實際上只是在初始分配期間預先分配nullfib的值,以便分配有效。

我更喜歡不必聲明null的方便,所以我喜歡這種行爲。儘管如此,這並不是真正的原創文章所談論的內容。

+0

你說得對。這不是「匿名遞歸」,因爲你使用的是「named」* fib *函數。 – 2011-03-30 15:46:33