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

问题描述

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

解决方法

单个构造函数变体的另一个特殊用例是使用 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 元素列表成为可能,即使每个元素具有不同的具体类型。

,

就其价值而言,在我看来,这在过去的 OCaml 中并不特别常见。

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

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

type [@unboxed] t = T of int

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

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

(所有这些自然只是我的意见。)

,

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

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

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" *)

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

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. *)

,

单构造函数(多态)变体的另一个用例是向函数的调用者记录一些东西。例如,您的函数返回的值可能有一个警告:

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

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

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

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

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