约束多态类型

发布于 2024-11-13 11:16:32 字数 728 浏览 2 评论 0原文

我的范围类型定义为:

type 'a range = Full | Range of ('a * 'a)

但是,我想将 'a 限制为整数、浮点数或字符,而 'a 没有其他有效类型。

Range(0,10) (* valid *)
Range(0.0, 10.0) (* valid *)
Range('a', 'z') (* valid *)
Range("string1", "string2") (* other types like this shouldn't type check *)

我认为我可以将类型定义更改为:

type sequential   = S_int of int | S_float of float | S_char of char ;;
type range = Full | Range of (sequential * sequential);;

但是,这将允许类似:

Range(S_int(0), S_float(10.0));; (* problem: mixes int and float *)

...但我希望 Range 的两个组件具有相同的类型。

我想另一种方法是创建一个 int_range 类型、一个 float_range 类型和一个 char_range 类型,但我想知道是否还有其他方法?

I've got a range type defined as:

type 'a range = Full | Range of ('a * 'a)

However, I'd like to constrain 'a to be integer or float or char, with no other valid types for 'a.

Range(0,10) (* valid *)
Range(0.0, 10.0) (* valid *)
Range('a', 'z') (* valid *)
Range("string1", "string2") (* other types like this shouldn't type check *)

I figured that I could change my type definitions to:

type sequential   = S_int of int | S_float of float | S_char of char ;;
type range = Full | Range of (sequential * sequential);;

However, this would then allow something like:

Range(S_int(0), S_float(10.0));; (* problem: mixes int and float *)

...but I want both components of Range to be the same type.

I suppose that another approach would be to create an int_range type, a float_range type, and a char_range type but I'm wondering if there's another way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

装纯掩盖桑 2024-11-20 11:16:32

另一种方法是将类型声明为私有并公开仅使用您想要的类型构造它的函数,例如:

module Z : sig
  type 'a range = private Full | Range of ('a * 'a)
  val int_range : int -> int -> int range
  val float_range : float -> float -> float range
  val string_range : string -> string -> string range
  val full : 'a range
end = struct
  type 'a range = Full | Range of ('a * 'a)
  let create x y = Range (x,y)
  let int_range = create
  let float_range = create
  let string_range = create
  let full = Full
end

# open Z;;
# int_range 2 3;;
- : int Z.range = Range (2, 3)
# Range ('a','c');;
Error: Cannot create values of the private type char Z.range

Another approach is to declare type private and expose functions constructing it only with the types you want, e.g. :

module Z : sig
  type 'a range = private Full | Range of ('a * 'a)
  val int_range : int -> int -> int range
  val float_range : float -> float -> float range
  val string_range : string -> string -> string range
  val full : 'a range
end = struct
  type 'a range = Full | Range of ('a * 'a)
  let create x y = Range (x,y)
  let int_range = create
  let float_range = create
  let string_range = create
  let full = Full
end

# open Z;;
# int_range 2 3;;
- : int Z.range = Range (2, 3)
# Range ('a','c');;
Error: Cannot create values of the private type char Z.range
桃扇骨 2024-11-20 11:16:32

从 Haskell 的做法中得到提示(声明一个类型类 (Sequential a) => Range a),您可以使用函子:

module Range (S : sig type t end) = struct
  type range = Full | Range of (S.t * S.t)
end

并使用它来提供所需的模块:

module IntRange   = Range (struct type t = int   end)
module FloatRange = Range (struct type t = float end)
module CharRange  = Range (struct type t = char  end)

缺点是您失去范围的参数性;好处是,您在 range 上的参数函数现在位于模块 Range 中,正如它们可能应该的那样。

一般来说,Range会对Sequential提出许多要求,以补偿参数性的损失。这些要求可以在函子参数的签名中清楚地指定:

module type SEQUENTIAL = sig
  type t
  val to_string : t -> string
  val compare : t -> t -> int
  (* ... *)
end

module Range (S : SEQUENTIAL) = struct
  type t = Full | Range of (S.t * S.t)
  let to_string = function
  | Full -> "full"
  | Range (lo, hi) -> "(" ^ S.to_string lo ^ "," ^ S.to_string hi ^ ")"
  let make lo hi =
    if S.compare lo hi > 0 then Range (hi, lo) else Range (lo, hi)
end

要在特定类型上实例化 Range,您现在需要提供一个正确参数化它的结构:

module IntRange = Range (struct
  type t = int
  let to_string = string_of_int
  let compare = Pervasives.compare
end)

然后您可以像这样使用它

# IntRange.(to_string (make 4 2)) ;;
- : string = "(2,4)"

:(使用新语法进行分隔重载)。如果您需要将 Range 的实现隐藏在签名后面,则可能需要重新导出 SEQUENTIAL 的类型,就像标准库中的数据结构一样do:

module Range (S : SEQUENTIAL) : sig
  type elt = S.t
  type t = private Full | Range of (elt * elt)
  val to_string : t -> string
  val make : elt -> elt -> t
end = struct
  type elt = S.t
  type t = Full | Range of (elt * elt)
  let to_string = function
  | Full -> "full"
  | Range (lo, hi) -> "(" ^ S.to_string lo ^ "," ^ S.to_string hi ^ ")"
  let make lo hi =
    if S.compare lo hi > 0 then Range (hi, lo) else Range (lo, hi)
end

这为您提供了可以进行模式匹配但不能构造的封装和半透明类型。在签名中声明私有类型的另一种方法是使用视图类型或解构函数。

Taking a hint from what Haskell would do (declare a type class (Sequential a) => Range a) you could use a functor:

module Range (S : sig type t end) = struct
  type range = Full | Range of (S.t * S.t)
end

and use it to provide the required modules:

module IntRange   = Range (struct type t = int   end)
module FloatRange = Range (struct type t = float end)
module CharRange  = Range (struct type t = char  end)

The downside is that you lose parametricity on range; the upside is that your parametric functions on ranges now live inside the module Range, as they probably should.

In general, Ranges will make a number of demands of Sequentials in order to compensate for the loss of parametricity. These requirements can be cleanly specified in the signature of the functor parameter:

module type SEQUENTIAL = sig
  type t
  val to_string : t -> string
  val compare : t -> t -> int
  (* ... *)
end

module Range (S : SEQUENTIAL) = struct
  type t = Full | Range of (S.t * S.t)
  let to_string = function
  | Full -> "full"
  | Range (lo, hi) -> "(" ^ S.to_string lo ^ "," ^ S.to_string hi ^ ")"
  let make lo hi =
    if S.compare lo hi > 0 then Range (hi, lo) else Range (lo, hi)
end

To instantiate the Range at a specific type you now need to provide a structure that properly parameterizes it:

module IntRange = Range (struct
  type t = int
  let to_string = string_of_int
  let compare = Pervasives.compare
end)

Then you can use it like this:

# IntRange.(to_string (make 4 2)) ;;
- : string = "(2,4)"

(using the new syntax for delimited overloading). If you need to hide the implementation of Ranges behind a signature, you might need to re-export the type of SEQUENTIALs, much as the data structures in the standard library do:

module Range (S : SEQUENTIAL) : sig
  type elt = S.t
  type t = private Full | Range of (elt * elt)
  val to_string : t -> string
  val make : elt -> elt -> t
end = struct
  type elt = S.t
  type t = Full | Range of (elt * elt)
  let to_string = function
  | Full -> "full"
  | Range (lo, hi) -> "(" ^ S.to_string lo ^ "," ^ S.to_string hi ^ ")"
  let make lo hi =
    if S.compare lo hi > 0 then Range (hi, lo) else Range (lo, hi)
end

This gives you encapsulation and translucent types that can be pattern-matched but not constructed. An alternative to declaring private types in the signature is to use a view type or a destructuring function.

好倦 2024-11-20 11:16:32

OMG 模块好复杂啊!

type 'a range' = [`Full | `Range of 'a * 'a]
type range = [
  | `Int_range of int range'
  | `Float_range of float range'
]

天啊,我们需要添加另一个:

type xrange = [
  | range
  | `String_range of string range'
]

OMG modules are so complicated!

type 'a range' = [`Full | `Range of 'a * 'a]
type range = [
  | `Int_range of int range'
  | `Float_range of float range'
]

Oh dang, we need to add another one:

type xrange = [
  | range
  | `String_range of string range'
]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文