ocaml,能够在值更改时触发编译错误
ocaml, ability to trigger compile error on value change
我想以编译时的方式表达,我的代码是在某个值是某个常量的假设下运行的。为简单起见,假设我有这个模块
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
不同时我想要一个编译错误,在这种情况下我想检查我的代码是否适合 differet 值。这样我就不必阅读 lib 的发行说明,编译器会给我回电话。
我对 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]
是顶级类型,也就是超类型。
对于 default
类型我们有选项,我们可以 post 它具有类型 ['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
因为我们明确表示default
是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 标记
我想以编译时的方式表达,我的代码是在某个值是某个常量的假设下运行的。为简单起见,假设我有这个模块
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
不同时我想要一个编译错误,在这种情况下我想检查我的代码是否适合 differet 值。这样我就不必阅读 lib 的发行说明,编译器会给我回电话。
我对 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]
是顶级类型,也就是超类型。
对于 default
类型我们有选项,我们可以 post 它具有类型 ['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
因为我们明确表示default
是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 标记