Ocaml 中是否有单个案例变体的用例?

Are there use cases for single case variants in Ocaml?

我一直在阅读 F# 文章,它们使用单​​一大小写变体来创建不同的不兼容类型。但是在 Ocaml 中,我可以使用私有模块类型或抽象类型来创建不同的类型。在 Ocaml 中是否常见使用 F# 或 Haskell 中的单大小写变体?

在我看来,这在过去的 OCaml 中并不是特别常见。

我自己一直不愿意这样做,因为它总是要付出一些代价:type t = T of int 的表示总是比 int 的表示大。

然而最近(可能是几年)可以将类型声明为未装箱,这消除了这个障碍:

type [@unboxed] t = T of int

因此,我个人最近更频繁地使用单一构造函数类型。有很多优点。对我来说最主要的是我可以有一个独特的类型,它独立于它的表示是否恰好与另一种类型相同。

你当然可以像你说的那样使用模块来达到这个效果。但这是一个相当沉重的解决方案。

(这一切只是我的自然看法。)

单个构造函数变体的另一个专门用例是使用 GADT(和存在量化)擦除某些类型信息。 例如,在

type showable = Show: 'a * ('a -> string) -> showable
let show (Show (x,f)) = f x
let showables = [ Show (0,string_of_int); Show("string", Fun.id) ]

构造函数Show 将给定类型的元素与打印函数配对,然后忘记元素的具体类型。这使得拥有 showable 个元素的列表成为可能,即使每个元素具有不同的具体类型。

单构造函数类型的另一种情况(尽管它与您最初创建不同类型的问题不太匹配):奇特的记录。 (与其他答案相比,这更像是一种语法上的便利,而不是一项基本功能。)

事实上,使用 relatively recent feature(在 2016 年随 OCaml 4.03 引入)允许使用记录语法(包括可变字段!)编写构造函数参数,您可以在常规记录前加上构造函数名称 Coq -style.

type t = MakeT of {
  mutable x : int ;
  mutable y : string ;
}

let some_t = MakeT { x = 4 ; y = "tea" }
(* val some_t : t = MakeT {x = 4; y = "tea"} *)

它不会在运行时改变任何东西(就像 Constr (a,b)(a,b) 具有相同的表示,前提是 Constr 是其类型的唯一构造函数)。构造函数使代码对人眼来说更明确一些,它还提供了消除字段名称歧义所需的类型信息,从而避免了类型注释的需要。它在功能上与通常的模块技巧相似,但更系统。

模式是一样的:

let (MakeT { x ; y }) = some_t
(* val x : int = 4 *)
(* val y : string = "tea" *)

您还可以访问“包含”记录(无运行时成本)、读取和修改其字段。然而,这个包含的记录不是第一个 class 值:你不能存储它,将它传递给一个函数,也不能 return 它。

let (MakeT fields) = some_t in fields.x (* returns 4 *)
let (MakeT fields) = some_t in fields.x <- 42
(* some_t is now MakeT {x = 42; y = "tea"} *)

let (MakeT fields) = some_t in fields
(*                             ^^^^^^
   Error: This form is not allowed as the type of the inlined record could escape. *)

单构造函数(多态)变体的另一个用例是向函数的调用者记录一些东西。例如,也许有一个警告,你的函数 returns:

val create : unit -> [ `Must_call_close of t ]

使用变体会强制函数的调用者在其代码中对该变体进行模式匹配:

let (`Must_call_close t) = create () in (* ... *)

这使得他们更有可能注意变体中的消息,而不是 .mli 文件中可能会被遗漏的文档。

对于这个用例,多态变体更容易使用,因为您不需要为变体定义中间类型。