2017-08-11 61 views
4

想象一下下面的代碼:F# - 爲什麼Seq.map不會傳播異常?

let d = dict [1, "one"; 2, "two" ] 

let CollectionHasValidItems keys = 
    try 
     let values = keys |> List.map (fun k -> d.Item k) 
     true 
    with 
     | :? KeyNotFoundException -> false 

現在讓我們來測試一下:

let keys1 = [ 1 ; 2 ] 
let keys2 = [ 1 ; 2; 3 ] 

let result1 = CollectionHasValidItems keys1 // true 
let result2 = CollectionHasValidItems keys2 // false 

這個工程,我期望的那樣。但是,如果我們在函數改變列表以序列,我們得到不同的行爲:

let keys1 = seq { 1 .. 2 } 
let keys2 = seq { 1 .. 3 } 

let result1 = CollectionHasValidItems keys1 // true 
let result2 = CollectionHasValidItems keys2 // true 

這裏有keys2我可以看到內對象在調試器,但沒有異常被拋出的異常的消息...

這是爲什麼?我需要在我的應用程序中使用類似的邏輯,並且傾向於使用序列。

+3

這是因爲[懶惰評價](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/lazy-computations)序列。試試'let values = keys |> Seq.map(fun k - > d.Item k)|> Seq.toList'。 – Funk

回答

6

這是副作用和懶惰評估問題的典型例子。 Seq功能如Seq.map是懶惰評估,這意味着Seq.map的結果將不計算,直到枚舉返回的序列。在你的例子中,這從來沒有發生,因爲你從來沒有對values做任何事情。

如果您通過生成一個具體的集合,就像一個list強制序列的評價,你會得到你的例外,該函數將返回false:使用List.map代替

let CollectionHasValidItems keys = 
    try 
     let values = keys |> Seq.map (fun k -> d.Item k) |> Seq.toList 
     true 
    with 
     | :? System.Collections.Generic.KeyNotFoundException -> false 

正如你已經注意到了, Seq.map也解決了你的問題,因爲它會在被調用時被熱切地評估,返回一個新的具體list

關鍵問題是,您必須非常小心將副作用與懶惰評估相結合。你不能依賴你最初期望的順序發生的效果。

+1

對,所以它不僅僅是關於F#,而是關於一般懶惰的評估(我剛剛在我的母語C#中嘗試過)。我讀了一點,現在它是有道理的。謝謝! – psfinaki

+0

@psfi​​naki是的,你可以直接用'IEnumerable'/LINQ把它翻譯成C#,你將得到完全一樣的行爲。 – TheInnerLight

+0

我認爲這裏很重要的一點是,這種失敗的根本原因是程序_relies_隱含的副作用(在這裏,'d.Item'會拋出異常的知識)而不是顯式地編碼意圖。 –