"Lifting" 选项类型的例外

"Lifting" exceptions to Option types

F# 和 Scala 都是一种混合语言,通常用于将传统的面向对象代码与函数式代码联系起来。

更多属于 OO 世界的概念是例外,而功能世界在许多情况下更喜欢 Option 类型。 为了包装依赖于异常的现有库代码 - 并使其更具功能性 - 因此我想 "lift" 异常抛出代码来代替 return 一个选项类型。

在 Scala 中,有一个很好的库函数可以 "catch all" 并转换为选项。可以这样使用:

import scala.util.control.Exception._
val functionalVersion = allCatch opt myFunction

In Scala, is there a pre-existing library function for converting exceptions to Options?

既然我要转向 F#,我也有同样的要求,但我似乎无法为此找到一个现有的实用函数 - 而且我自己也很难实现一个。

我可以为一个单元函数创建这样一个包装器,也就是一个动作

let catchAll f = try Some (f()) with | _ -> None

但这里的问题是我不想首先将所有异常抛出代码包装到一个动作中。

例如,我想包装数组索引运算符,这样它就不会抛出异常。

// Wrap out-of-bounds exception with option type
let maybeGetIndex (array: int[]) (index: int) = catchAll (fun () -> array.[index])

maybeGetIndex [| 1; 2; 3 |] 10 // -> None

不过如果能简单写一下就更好了

(catchAll a.[index])

即在计算之前将 catchAll 应用于整个表达式。 (Scala 可以通过 call-by-name parameters 实现,而 F# 似乎没有)

所以这个问题是双重的:

  1. 是否有现有的库函数将异常包装到选项中 类型?
  2. 是否有一种语言功能可以让我实现 是吗?

首先,我认为"concept that belongs more to the object-oriented world are exceptions"是不正确的。异常存在于 ML 家族的许多函数式语言中,例如,OCaml 非常依赖它们,甚至将它们用于某些控制流结构。在 F# 中,情况并非如此,因为 .NET 异常有点慢,但我看到异常与 object-oriented/functional 问题非常正交。

出于这个原因,我实际上发现异常通常比 F# 中的选项类型更可取。缺点是类型检查较少(您不知道会抛出什么),但优点是该语言为异常提供了很好的集成语言支持。如果您需要处理 异常 情况,那么异常是一个很好的方法!

为了回答您关于语法技巧的原始问题 - 我可能会像您现有的代码那样使用一个函数,因为它是明确且易于理解的(并且大概您只需要在某些地方进行异常包装您实现的核心功能)。

就是说,您可以定义一个计算表达式构建器,将代码包装在主体中并作为您的 catchAll 函数,语法更简洁:

type CatchAllBuilder() = 
  member x.Delay(f) = try Some(f()) with _ -> None
  member x.Return(v) = v

let catchAll = CatchAllBuilder()

这让你可以这样写:

catchAll { return Array.empty.[0] }

如前所述,我不会这样做,因为 (i) 我认为 F# 中不需要将所有异常都转换为选项,并且 (ii) 它引入了新团队成员(以及未来的您)不熟悉的语法) 可能会被混淆,但这可能是您可以获得的最好的语法。

[编辑:现在是 return 的工作版本 - 这有点不那么漂亮,但也许仍然有用!]