想象一下以下代码:
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
这正如我所料。但是如果我们在函数中将List更改为Seq,我们会得到不同的行为:
let keys1 = seq { 1 .. 2 }
let keys2 = seq { 1 .. 3 }
let result1 = CollectionHasValidItems keys1 // true
let result2 = CollectionHasValidItems keys2 // true
这里使用 keys2 我可以在调试器的 values 对象中看到异常消息,但不会抛出任何异常......
为什么会这样?我的应用程序中需要一些类似的逻辑,并且更喜欢使用序列。
答案 0 :(得分:6)
这是副作用和懒惰评估问题的典型示例。对Seq
等Seq.map
函数进行了延迟求值,这意味着在枚举返回的序列之前,不会计算Seq.map
的结果。在您的示例中,这种情况永远不会发生,因为您从未对values
执行任何操作。
如果通过生成具体集合强制评估序列,如list
,您将获得异常,函数将返回false
:
let CollectionHasValidItems keys =
try
let values = keys |> Seq.map (fun k -> d.Item k) |> Seq.toList
true
with
| :? System.Collections.Generic.KeyNotFoundException -> false
正如您所注意到的,使用List.map
代替Seq.map
也可以解决您的问题,因为在调用时会对其进行急切评估,并返回一个新的具体list
。
关键的一点是,你必须非常小心将副作用与懒惰评估结合起来。你不能依赖于你最初期望的顺序发生的效果。