F# - 为什么 Seq.map 不传播异常?
F# - Why Seq.map does not propagate exceptions?
想象一下下面的代码:
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 对象中看到异常消息,但没有抛出异常...
为什么会这样?我的应用程序中需要一些类似的逻辑,并且更愿意使用序列。
这是一个典型的副作用和惰性求值问题的例子。 Seq
函数,例如 Seq.map
是惰性求值的,这意味着 Seq.map
的结果将不会被计算,直到 returned 序列被枚举。在您的示例中,这永远不会发生,因为您从不对 values
.
做任何事情
如果您通过生成一个具体的集合来强制计算序列,例如 list
,您将得到异常并且该函数将 return 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
也解决了您的问题,因为它会在调用时急切地求值,returning 一个新的混凝土 list
.
关键要点是,在将副作用与惰性求值相结合时,您必须非常小心。您不能依赖于按您最初预期的顺序发生的效果。
想象一下下面的代码:
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 对象中看到异常消息,但没有抛出异常...
为什么会这样?我的应用程序中需要一些类似的逻辑,并且更愿意使用序列。
这是一个典型的副作用和惰性求值问题的例子。 Seq
函数,例如 Seq.map
是惰性求值的,这意味着 Seq.map
的结果将不会被计算,直到 returned 序列被枚举。在您的示例中,这永远不会发生,因为您从不对 values
.
如果您通过生成一个具体的集合来强制计算序列,例如 list
,您将得到异常并且该函数将 return 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
也解决了您的问题,因为它会在调用时急切地求值,returning 一个新的混凝土 list
.
关键要点是,在将副作用与惰性求值相结合时,您必须非常小心。您不能依赖于按您最初预期的顺序发生的效果。