2017-08-11 52 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'会抛出异常的知识)而不是显式地编码意图。 –