Exception F#-为什么Seq.map不传播异常?

Exception F#-为什么Seq.map不传播异常?,exception,collections,f#,mapping,sequences,Exception,Collections,F#,Mapping,Sequences,想象一下下面的代码: 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

想象一下下面的代码:

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
的结果。在您的示例中,这种情况从未发生,因为您从未使用
值执行任何操作

如果通过生成具体的集合(如
列表
)来强制对序列求值,则会得到异常,函数将返回
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


关键的一点是,在将副作用与惰性评估相结合时,你必须非常小心。你不能依赖于效果按照你最初预期的顺序发生。

这是因为顺序不同。试试看
let values=keys |>Seq.map(fun k->d.Item k)|>Seq.toList
。好吧,所以这不仅仅是关于F,而是关于一般的惰性评估(我刚刚用母语C尝试过)。我读了一点,现在它有意义了。谢谢大家!@psfinaki是的,你可以用
IEnumerable
/LINQ直接将其转换成C#,你会得到完全相同的行为。我认为需要注意的是,这一失败的根本原因是程序依赖于隐式副作用(这里,知道
d.Item
将引发异常)我不是显式地对意图进行编码。@FyodorSoikin我有点明白你的意思,但我认为所有的副作用都是隐含的。F#
async
和Haskell
IO
是我将其归入显式效果范畴的例子,使用这种方法可以安全地构建这个例子。除此之外,除了在结合副作用和惰性评估时非常小心或完全避免之外,没有太多的解决方案。问题不在于隐含副作用本身的存在,而是程序依赖它们进行计算。