【问题标题】:F# - Why Seq.map does not propagate exceptions?F# - 为什么 Seq.map 不传播异常?
【发布时间】:2017-08-11 10:58:03
【问题描述】:

想象一下下面的代码:

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 对象中看到异常消息,但没有抛出异常...

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

【问题讨论】:

  • 这是因为序列的lazy evaluation。试试let values = keys |> Seq.map (fun k -> d.Item k) |> Seq.toList

标签: exception collections f# mapping sequences


【解决方案1】:

这是一个典型的副作用和惰性求值问题的例子。 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

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

【讨论】:

  • 对,所以这不仅仅是关于 F#,而是关于一般的惰性评估(我刚刚用我的母语 C# 尝试过)。我读了一点,现在它是有道理的。谢谢!
  • @psfi​​naki 是的,您可以使用 IEnumerable/LINQ 直接将其转换为 C#,您将获得完全相同的行为。
  • 我认为这里需要注意的是,这个失败的根本原因是程序依赖隐式副作用(这里,知道d.Item 会抛出一个异常)而不是显式编码意图。
  • @FyodorSoikin 我有点明白你在说什么,但我认为所有副作用都是隐含的定义。 F# async 和 Haskell IO 是我放入显式效果类别的示例,并且可以使用这种方法安全地构建此示例。除此之外,除了在结合副作用和惰性评估时非常小心或完全避免它之外,没有很多解决方案。
  • 问题不在于隐含副作用的存在,而是程序依赖进行计算。
猜你喜欢
  • 2011-03-08
  • 1970-01-01
  • 1970-01-01
  • 2016-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
  • 1970-01-01
相关资源
最近更新 更多