选项类型的“提升”例外

时间:2019-01-15 10:01:52

标签: scala functional-programming f# option-type

F#和Scala都是一种混合语言,通常用于将传统的面向对象代码的单词桥接到​​功能代码。

一个更属于OO世界的概念是个例外,而在许多情况下,功能世界更喜欢Option类型。 为了包装依赖于异常的现有库代码并使之更具功能性,我因此想“举起”抛出异常的代码,而不是返回一个选项类型。

在Scala中,有一个不错的库函数可以“全部捕获”并转换为option。可以这样使用:

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. 是否存在将异常包装到option中的现有库函数 类型?
  2. 是否有一种语言功能可以让我实施 它吗?

1 个答案:

答案 0 :(得分:5)

首先,我认为“更属于面向对象世界的概念是例外” 是不正确的。 ML系列的许多功能语言中都存在异常,例如,OCaml非常依赖于它们,甚至将它们用于某些控制流结构。在F#中,情况并非如此,因为.NET异常的速度要慢一些,但是我看到异常非常与面向对象/功能问题正交。

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

要回答有关语法技巧的原始问题,我可能会像现有代码一样使用一个函数,因为这是显式且易于理解的(大概,您只需要在某些核心函数中进行异常包装即可)实施)。

也就是说,您可以定义一个计算表达式构建器,该构建器将代码包装在正文中并用作您的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的工作版本-看上去不太漂亮,但也许仍然有用!]