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

psf*_*aki 4 mapping collections f# sequences exception

想象一下以下代码:

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
Run Code Online (Sandbox Code Playgroud)

现在让我们测试一下:

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

let result1 = CollectionHasValidItems keys1 // true
let result2 = CollectionHasValidItems keys2 // false
Run Code Online (Sandbox Code Playgroud)

这可以像我期望的那样工作.但是如果我们在函数中将List更改为Seq,我们会得到不同的行为:

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

let result1 = CollectionHasValidItems keys1 // true
let result2 = CollectionHasValidItems keys2 // true
Run Code Online (Sandbox Code Playgroud)

这里使用keys2我可以在调试器的values对象中看到异常消息但是没有抛出异常......

为什么会这样?我的应用程序中需要一些类似的逻辑,并且更喜欢使用序列.

The*_*ght 6

这是副作用和懒惰评估问题的典型示例. 延迟评估的Seq函数Seq.map,这意味着在Seq.map枚举返回的序列之前不会计算结果.在你的例子中,这永远不会发生,因为你从不做任何事情values.

如果你通过生成一个具体的集合强制评估序列,比如a 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
Run Code Online (Sandbox Code Playgroud)

正如您所注意到的那样,使用List.map而不是Seq.map解决您的问题,因为在调用它时会急切地评估它,返回一个新的具体内容list.

关键的一点是,你必须非常小心将副作用与懒惰评估结合起来.您不能依赖于您最初期望的顺序发生的效果.

  • @FyodorSoikin 我有点明白你在说什么,但我认为所有的副作用都是隐含的。F# `async` 和 Haskell `IO` 是我放入显式效果类别的示例,并且可以使用这种方法安全地构建此示例。除此之外,除了在结合副作用和懒惰评估或完全避免它时非常小心之外,没有很多解决方案。 (2认同)