ocaml,能够在值更改时触发编译错误

标签 ocaml compile-time type-safety

我想以编译时的方式表达,我的代码在假设值是某个常量的情况下运行。为了简单起见,假设我有这个模块

module Lib : sig
  type t = A|B|C|D
  val default : t
  val f : t option -> unit
end = struct
  type t = A|B|C|D
  let default = B
  let f _ = ()
end

我在Lib外部编写代码,并希望以编译时方式断言,我需要默认值为B。这意味着当 Lib.default 与 B 不同时,我希望出现编译错误,在这种情况下,我想检查我的代码是否适合不同的值。这样我就不必阅读库的发行说明,编译器会回调我。

我对Lib有一定的控制权,因此我可以根据需要更改它。我对构建它的其他方式感兴趣,如果这能让编译时断言更容易,更不用说可能了。

我的代码的其他部分不依赖于此,例如

let config : Lib.t option =
  match Lib.default with
  | A
  | B
  | C -> None
  | D -> Some C

我正在考虑做子类型,比如

type t = [`A|`B|`C|`D]
val default : [`B]

但随后我删除了 default 可能会更改为 t 的其他构造函数的信息,然后这将编译错误,指出匹配 A是不可能的。

let config : Lib.t option =
  match Lib.default with
  | `A
  | `B
  | `C -> None
  | `D -> Some `C

谢谢

最佳答案

子类型可能是一个解决方案,尽管我不得不承认我没有完全理解你想要什么。但稍后,让我们首先讨论多态变体和子类型。您的尝试尚未使用子类型,因为您的类型中不存在多态性,即 type t = ['A|'B|'C|'D]1是地面类型。我们需要的是以下内容,

module Lib : sig
  type 'a t = [< `A|`B|`C|`D] as 'a
  val default : [ `B] t
  val f : 'a t option -> unit
end = struct
  type 'a t = [< `A|`B|`C|`D] as 'a
  let default = `B
  let f _ = ()
end

因此我们说 'a Lib.t 是一个类型族,'a t 类型的值可以是 ['A]'B['A|'B]['A|'B|'C] 或 ... [A|B|C|D] 是顶级类型,也称为父类(super class)型。

使用 default 类型,我们可以选择,我们可以发布它的类型 ['B] t,这与 ['B] 相同,但更清楚地表明它是层次结构的一部分,因此用户应该期望它更改为任何其他类型。从类型系统的角度来看,这并不重要,因为 OCaml 类型系统不是名义上的,而是结构上的。

这个解决方案会给你一个类型错误,

let config : _ Lib.t option =
  match Lib.default with
  | `A  (* default is not polymorphic and can be only `B *)
  | `B
  | `C -> None
  | `D -> Some `C

因为我们明确指出默认是B且仅是B。

或者,我们可以说 default 可以是 [> 'B],即它是一个多态类型,至少是 B,但可以是其他任何类型。使用此解决方案,您将不会在 config 函数中遇到任何错误。例如,如果您从 [> 'B] 更改为 [> 'A],您也不会收到任何错误。所以它可能不是您想要的,所以让我们返回并使用单态 ['B] 类型作为默认值,并尝试在用户端处理它。我们可以明确地说,我们希望将基本默认值向上转换为所有可能的值,例如

module Lib : sig
  type 'a t = [< `A|`B|`C|`D] as 'a
  val default : [`B] t
  val f : 'a t option -> unit
end = struct
  type 'a t = [< `A|`B|`C|`D] as 'a
  let default = `B
  let f _ = ()
end

let config : _ Lib.t option =
  match (Lib.default : [`B] Lib.t :> [> `B] Lib.t) with
  | `A
  | `B
  | `C -> None
  | `D -> Some `C

现在,如果我们将默认值更改为 A,我们将得到所需的类型错误。唯一需要注意的是,我们需要在每个用例中指定当前验证的默认值,因此让我们将其移至 Lib,例如

module Lib : sig
  type 'a t = [< `A|`B|`C|`D] as 'a
  type verified = [`B]
  val default : [`B] t
  val f : 'a t option -> unit
end = struct
  type 'a t = [< `A|`B|`C|`D] as 'a
  type verified = [`B] t
  let default = `B
  let f _ = ()
end

open Lib
let config : _ Lib.t option =
  match (default : verified t :> [> verified ] t) with
  | `A
  | `B
  | `C -> None
  | `D -> Some `C

所以现在,当您想尝试新的默认值时,您可以更改默认值的类型(当然还有值),但不要更改 verified 类型并完成所有使用 -直到您准备好将新添加的类型添加到验证集中。是的,设置,因为我们可以升级已验证的类型以接受一组变体,例如,

module Lib : sig
  type 'a t = [< `A|`B|`C|`D] as 'a
  type verified = [`A |`B]
  val default : [`B] t
  val f : 'a t option -> unit
end = struct
  type 'a t = [< `A|`B|`C|`D] as 'a
  type verified = [`A|`B] t
  let default = `B
  let f _ = ()
end

open Lib
let config : _ Lib.t option =
  match (default : [< verified] t :> [> verified ] t) with
  | `A
  | `B
  | `C -> None
  | `D -> Some `C

现在,如果 Lib.default 具有 A 或 B 以外的任何变体,我们将收到错误。作为奖励,您无需在使用站点上更改任何内容。

作为最后的改进,我建议摆脱名义上的(在这个词的所有意义上)'a t 类型,而只需要多态类型,一个用于一组经过验证的祝福构造函数另一个用于所有可能的构造函数的集合,例如,

module Lib : sig
  type 'a default = [> `A|`B|`C|`D] as 'a
  type 'a verified = [< `A |`B] as 'a
  val default : [`B]
  val f : 'a default option -> unit
end = struct
  type 'a default = [> `A|`B|`C|`D] as 'a
  type 'a verified = [< `A|`B] as 'a
  let default = `B
  let f _ = ()
end

open Lib
let config : _ option =
  match (default : _ verified :> _ default) with
  | `A
  | `B
  | `C -> None
  | `D -> Some `C

let config : 'b option =
  match (default : 'a verified :> 'b default) with
  | `A
  | `B
  | `C -> None
  | `D -> Some `C

1)) 请原谅我错误的反引号,正确的反引号在 SO 标记中表现不佳

关于ocaml,能够在值更改时触发编译错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70186619/

相关文章:

字符串的 OCaml 函数参数模式匹配

张量的 C++ 编译时索引/元组访问

实现特定接口(interface)的 "Type"的C#列表

java - 在java中的通用类内部进行转换

emacs - 无法向 OCaml 顶层和 coqtop(和 Proof General)提供长(1024+ 个字符)输入

ocaml - 如何防止 "- : unit = ()"在 ocaml 顶层输出后出现?

ocaml - 多参数类型

c++ - const char* 不能用作 std::char_traits<char>::length 的常量值

java - 标准 Api 与 QueryDsl 与 JPA 元模型

c++ - 有没有一种方法可以机械地识别哪些操作对移出的对象是安全的?