假设,我想写一个试图在地图中找到一个键的函数,如果它不能,则返回None
:try_find: 'a -> ('a, 'b) Map.t -> 'b option
,这样做的规范方法是什么?要首先检查密钥是否存在于mem
,然后调用find
?或者要抓住Not_found
例外?电池似乎是后者。
另一方面,在C#或Java等语言中,出于性能原因,通常不鼓励人们在这种情况下使用异常。在"正常"上使用例外执行路径在Ocaml中是常见的事情还是不鼓励?
答案 0 :(得分:3)
从我所看到的情况来看,OCaml异常非常有效,而且我认为它们的使用频率比其他函数式语言更频繁。
我尽量避免使用它们,因为它们干扰了程序的推理。但是库中的独立使用并不是那么糟糕。
异常等低级别事物的效率可能因平台而异。我怀疑对于非常大的地图来说捕捉Not_found
异常会更快,因为它避免了两次遍历地图。否则它可能没那么重要。
答案 1 :(得分:3)
OCaml异常与默认后端的函数调用一样快。对于Javascript后端,并不总是如此。规范的OCaml方法是实现一个不抛出异常的函数是使用throw函数并将异常转换为一个nullary变量,例如,
let try_find x xs = try Some (List.find x xs) with Not_found -> None
调用mem
和find
会导致性能下降,因为您实际上会迭代列表两次。
在提出异常和返回选项类型之间存在权衡。标准函数List.find
不会在堆中分配任何新值,因此不会创建任何垃圾。另一方面,try_find
函数将在每次找到某个内容时分配一个新值(None
是常量,因此不会分配)。这将为垃圾收集器创建额外的工作,最终会降低性能。对我来说,总功能的语义优势超过了可能的性能下降。如果后者确实重要(在紧密循环的情况下),那么我总是可以通过在非常紧密的上下文中使用异常或者继续传递样式和/或GADT来在本地优化它。
在“正常”执行路径上使用异常是Ocaml中常见的事情还是不鼓励?
语言的设计没有气馁,OCaml标准库使用了很多例外。但是,语言不断发展,并且语言中添加了新功能。此外,还实现了新的后端,例如几个Javascript后端,Java和.Net后端。为这些后端提供相同的性能保证并非易事。因此,随着时间的推移,异常的流行度降低了,许多人开始倾向于使用明确编码的错误来支持总函数,参见新标准库result
类型。另一个例子是Janestreet核心库(和所有其他库)不赞成异常并仅在特殊情况下使用它们。
您应自行决定例外政策(或借用现有政策)。我的个人政策是试图在公共界面中避免使用它们,并在本地使用它们。对于逻辑和程序员错误,我也使用异常,基本上是错误,不应该捕获。