约束多态类型

时间:2011-06-03 04:52:49

标签: types functional-programming ocaml

我有一个范围类型定义为:

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类型,但我想知道是否还有另一种方式?

3 个答案:

答案 0 :(得分:12)

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

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

答案 1 :(得分:10)

从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内部,因为它们可能应该存在。

通常,Range会对Sequential s提出一些要求,以弥补参数的损失。可以在仿函数参数的签名中清晰地指定这些要求:

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 s的实现,则可能需要重新导出SEQUENTIAL的类型,就像标准库中的数据结构一样:

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

这为封装和半透明类型提供了模式匹配但不构造的类型。在签名中声明private类型的替代方法是使用视图类型或解构函数。

答案 2 :(得分:0)

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'
]