41

我注意到,在我认识的 OCaml 程序员中,他们中的一些人总是使用多态变体(未声明的变体,以反引号为前缀),而其他人从不使用多态变体,并且更喜欢在类型中声明的变体。

除了性能原因(目前多态变体的编译效率低于简单变体),OCaml 专家开发人员如何在它们之间进行选择?

4

3 回答 3

43

我的用法可以分为以下5类。1. 界面 2. 模块化 3. 易读性 4. 简洁 5. 技巧

  1. 如果变体类型仅在模块内部,我使用常规变体,因为正如您所说,它们的编译效率更高。
  2. 如果在接口中导出了变体类型,并且我觉得某些情况可能会出现在其他模块中,但让它们依赖于模块并不一定有意义,我使用多态变体,因为它们不绑定到模块命名空间系统. 示例: 的编码类型类型Xmlm。此外,将信号类型作为变体类型意味着您可以使用相同的 XML 处理思想开发模块,而无需引入对Xmlm.
  3. 如果在接口中导出变体类型,我发现当将变体类型的值赋予模块的函数时,使用常规变体有时过于冗长。示例: 的版本类型Uuidm不必编写,Uuidm.create Uuidm.V4您可以简单地编写Uuidm.create `V4,这样清晰且不那么冗长。
  4. 有时一个特定的函数可能会返回不同的情况。如果这些情况仅由该函数使用,我在接口中声明函数类型,而无需引入类型定义。例如parse : string -> [`Error of string | `Ok of t]
  5. 多态变体及其子类型允许您使用幻像类型静态地强制执行不变量。除了以增量方式定义它们的可能性之外,对于静态执行不变量和出于文档目的而言,它们也是有用的。

最后,我有时会根据 4. 在模块的实现中使用多态变体,但它们不会出现在界面中。我不鼓励这种用法,除非您声明多态变体并关闭它们,因为它会削弱静态类型规则。

于 2012-02-20T20:13:24.220 回答
17

我在大多数模块接口中使用多态变体的唯一原因是为了解决经典变体的命名问题。

如果以下方法可行,那么在大多数情况下,多态变体将不再有用:

类型 t1 = 字符串 | 整数 | 布尔值 | t1列表列表
类型 t2 = 字符串 | 整数 | 其他

让 x =
  匹配 (x : t1) 与
      字符串 s -> 字符串 s
    | 诠释 n -> 诠释 n
    | 布尔_
    | 列表_->其他

2014-02-21 更新:上面的代码现在在 OCaml 4.01 中有效。欢呼!

于 2012-02-20T19:55:41.370 回答
13

多态变体总是效率较低的说法是不正确的。使用马丁的例子:

type base = [`String of string | `Int of int]
type t1 = [base | `Bool of bool | `List of t1 list]
type t2 = [base | `Other]

let simplify (x:t1):t2 = match x with
| #base as b -> b
| `Bool _ | `List _ -> `Other

要使用标准变体做到这一点,需要两种不同的类型和完整的重新编码,对于多态变体,基本情况是物理不变的。当使用开放递归进行术语重写时,此功能真正发挥作用:

type leaf = [`String of string | `Int of int]
type 'b base = [leaf | `List of 'b list]
type t1 = [t1 base | `Bool of bool ]
type t2 = [t2 base | `Other]

let rec simplify (x:t1):t2 = match x with
| #leaf as x -> x
| `List t -> `List (List.map simplify t)
| `Bool _ -> `Other

当重写函数也用开放递归分解时,优势就更大了。

不幸的是,Ocaml 的 Hindley-Milner 类型推断在没有显式类型的情况下不足以完成这种事情,这需要对类型进行仔细的因式分解,这反过来又使原型设计变得困难。此外,有时需要显式强制。

这种技术的最大缺点是,对于具有多个参数的术语,很快就会以相当混乱的类型组合爆炸而告终,最终更容易放弃静态强制并使用带有通配符和异常的厨房水槽类型(即动态类型)。

于 2012-02-26T19:05:02.577 回答