Ocaml 在另一个类型声明中选择一个类型的子类型
Ocaml selecting a type's subtype in another type declaration
给定两个类型声明,我想在另一个类型声明中使用一个类型声明的子类型。例如,假设我有红色、蓝色、黄色三种颜色,在制作另一种类型时我将如何具体引用每个子类型?此示例并非针对我的问题,而是对我面临的问题的简化。我试过下面的例子,直接引用红色等。我也试过红色,即:
type colour =
| Red
| Blue
| Yellow
type shape =
| Rectangle * Red
| Square * Yellow
请注意上面我是如何尝试为矩形强制使用红色颜色类型和为正方形强制使用黄色颜色类型的,我该怎么做呢?
如果您真的想将 Rectangle
和 Square
限制为仅一种颜色,则不需要表示该颜色——这会是多余的。但我假设你问的是比这更笼统的问题。
OCaml 不支持对此类变体进行子类型化。您不能创建仅具有 Red
作为其可能值的新类型,或仅具有 Red
和 Yellow
.
但是,所谓的 "polymorphic variants" 支持子类型化。你可以有这样的东西:
# type rby = [ `Red | `Blue | `Yellow ];;
type rby = [ `Blue | `Red | `Yellow ]
# type r = [ `Red ];;
type r = [ `Red ]
# type y = [`Yellow ];;
type y = [ `Yellow ]
# type shape = Rectangle of r | Square of y;;
type shape = Rectangle of r | Square of y
# Rectangle `Yellow;;
Error: This expression has type [> `Yellow ]
but an expression was expected of type r
The second variant type does not allow tag(s) `Yellow
# Rectangle `Red;;
- : shape = Rectangle `Red
请注意,OCaml 不会自动推断子类型关系。您需要使用 :>
表示法明确要求它们。
根据我的经验,多态变体会为您的代码增加很多复杂性。所以我建议只有在它们确实以其他方式使事情变得更好时才使用它们。
(我还要补充一点,您的类型 colour
或多或少与 C 或 Java 中的枚举完全相同。因此,您的要求并不完全清楚。没有C 中的方式或 Java 创建一个新类型,该类型仅包含从枚举中选择的几个值。)
在 OCaml 中,一种限制一组值的方法是通过模块系统来实现。模块是类型定义、命名值和子模块的集合。每个模块都公开一个接口,用于保证值的格式正确。
在此处的示例中,我们将有一个 Rectangle
模块,它会提供一个 create
函数,让您只能创建红色矩形。我们还有一个 Square
模块,它同样只会让用户创建黄色方块。
type color =
| Red
| Blue
| Yellow
module type Shape = sig
type t
val create : unit -> t
val get_color : t -> color
end
module Rectangle : Shape = struct
type t = unit
let color = Red
let create () = ()
let get_color () = color
end
module Square : Shape = struct
type t = unit
let color = Yellow
let create () = ()
let get_color () = color
end
type shape =
| Rectangle of Rectangle.t
| Square of Square.t
let get_color shape =
match shape with
| Rectangle x -> Rectangle.get_color x
| Square x -> Square.get_color x
现在我们有了矩形和正方形,每个都有自己的约束。形状是红色矩形或黄色正方形。这是由模块接口 Shape
保证的,它恰好由 Rectangle
和 Square
模块共享(但不是必须)。此模块接口强制您使用 create
函数来创建类型 t
的对象。请注意,Rectangle.t
和 Square.t
是不同的类型,不能互换使用,即使模块 Rectangle
和 Square
具有相同的接口。
如果您在这一点上没有迷路并且想进一步使用此解决方案,我建议您查看 private
关键字,它允许以只读方式公开类型详细信息。
这是一个幻像类型的例子:
type colour = Red | Blue | Yellow
type shape = Rectangle | Square
module ColouredShape : sig
(* Type parameterized by 'a, just for the type system. 'a does not appear in the
right hand side *)
(* Dummy types, used as labels in the phantom type *)
type 'a t
type red
type yellow
val make_red : shape -> red t
val make_yellow : shape -> yellow t
val make_rectangle : unit -> red t
val make_square : unit -> yellow t
val f : 'a t -> colour
val g : red t -> colour
val h : yellow t -> colour
end
=
struct
type 'a t = shape * colour
type red
type yellow
let make_red s = (s, Red)
let make_yellow s = (s, Yellow)
let make_rectangle () = make_red Rectangle
let make_square () = make_yellow Square
let f x = snd x
let g x = snd x
let h x = snd x
end
open ColouredShape
open Printf
let _ =
let rectangle = make_rectangle () in
let square = make_square () in
let c = f square in
printf "%b\n" (c = Red);
let c = f rectangle in
printf "%b\n" (c = Red);
let c = g square in
printf "%b\n" (c = Red);
let c = g rectangle in
printf "%b\n" (c = Red);
let c = h square in
printf "%b\n" (c = Red);
let c = h rectangle in
printf "%b\n" (c = Red)
正如预期的那样,编译器拒绝了代码:
let c = g square
^^^^^^
Error: This expression has type ColouredShape.yellow ColouredShape.t
but an expression was expected of type
ColouredShape.red ColouredShape.t
Type ColouredShape.yellow is not compatible with type
ColouredShape.red
这个答案是在 glennsl 的帮助下写的:
给定两个类型声明,我想在另一个类型声明中使用一个类型声明的子类型。例如,假设我有红色、蓝色、黄色三种颜色,在制作另一种类型时我将如何具体引用每个子类型?此示例并非针对我的问题,而是对我面临的问题的简化。我试过下面的例子,直接引用红色等。我也试过红色,即:
type colour =
| Red
| Blue
| Yellow
type shape =
| Rectangle * Red
| Square * Yellow
请注意上面我是如何尝试为矩形强制使用红色颜色类型和为正方形强制使用黄色颜色类型的,我该怎么做呢?
如果您真的想将 Rectangle
和 Square
限制为仅一种颜色,则不需要表示该颜色——这会是多余的。但我假设你问的是比这更笼统的问题。
OCaml 不支持对此类变体进行子类型化。您不能创建仅具有 Red
作为其可能值的新类型,或仅具有 Red
和 Yellow
.
但是,所谓的 "polymorphic variants" 支持子类型化。你可以有这样的东西:
# type rby = [ `Red | `Blue | `Yellow ];;
type rby = [ `Blue | `Red | `Yellow ]
# type r = [ `Red ];;
type r = [ `Red ]
# type y = [`Yellow ];;
type y = [ `Yellow ]
# type shape = Rectangle of r | Square of y;;
type shape = Rectangle of r | Square of y
# Rectangle `Yellow;;
Error: This expression has type [> `Yellow ]
but an expression was expected of type r
The second variant type does not allow tag(s) `Yellow
# Rectangle `Red;;
- : shape = Rectangle `Red
请注意,OCaml 不会自动推断子类型关系。您需要使用 :>
表示法明确要求它们。
根据我的经验,多态变体会为您的代码增加很多复杂性。所以我建议只有在它们确实以其他方式使事情变得更好时才使用它们。
(我还要补充一点,您的类型 colour
或多或少与 C 或 Java 中的枚举完全相同。因此,您的要求并不完全清楚。没有C 中的方式或 Java 创建一个新类型,该类型仅包含从枚举中选择的几个值。)
在 OCaml 中,一种限制一组值的方法是通过模块系统来实现。模块是类型定义、命名值和子模块的集合。每个模块都公开一个接口,用于保证值的格式正确。
在此处的示例中,我们将有一个 Rectangle
模块,它会提供一个 create
函数,让您只能创建红色矩形。我们还有一个 Square
模块,它同样只会让用户创建黄色方块。
type color =
| Red
| Blue
| Yellow
module type Shape = sig
type t
val create : unit -> t
val get_color : t -> color
end
module Rectangle : Shape = struct
type t = unit
let color = Red
let create () = ()
let get_color () = color
end
module Square : Shape = struct
type t = unit
let color = Yellow
let create () = ()
let get_color () = color
end
type shape =
| Rectangle of Rectangle.t
| Square of Square.t
let get_color shape =
match shape with
| Rectangle x -> Rectangle.get_color x
| Square x -> Square.get_color x
现在我们有了矩形和正方形,每个都有自己的约束。形状是红色矩形或黄色正方形。这是由模块接口 Shape
保证的,它恰好由 Rectangle
和 Square
模块共享(但不是必须)。此模块接口强制您使用 create
函数来创建类型 t
的对象。请注意,Rectangle.t
和 Square.t
是不同的类型,不能互换使用,即使模块 Rectangle
和 Square
具有相同的接口。
如果您在这一点上没有迷路并且想进一步使用此解决方案,我建议您查看 private
关键字,它允许以只读方式公开类型详细信息。
这是一个幻像类型的例子:
type colour = Red | Blue | Yellow
type shape = Rectangle | Square
module ColouredShape : sig
(* Type parameterized by 'a, just for the type system. 'a does not appear in the
right hand side *)
(* Dummy types, used as labels in the phantom type *)
type 'a t
type red
type yellow
val make_red : shape -> red t
val make_yellow : shape -> yellow t
val make_rectangle : unit -> red t
val make_square : unit -> yellow t
val f : 'a t -> colour
val g : red t -> colour
val h : yellow t -> colour
end
=
struct
type 'a t = shape * colour
type red
type yellow
let make_red s = (s, Red)
let make_yellow s = (s, Yellow)
let make_rectangle () = make_red Rectangle
let make_square () = make_yellow Square
let f x = snd x
let g x = snd x
let h x = snd x
end
open ColouredShape
open Printf
let _ =
let rectangle = make_rectangle () in
let square = make_square () in
let c = f square in
printf "%b\n" (c = Red);
let c = f rectangle in
printf "%b\n" (c = Red);
let c = g square in
printf "%b\n" (c = Red);
let c = g rectangle in
printf "%b\n" (c = Red);
let c = h square in
printf "%b\n" (c = Red);
let c = h rectangle in
printf "%b\n" (c = Red)
正如预期的那样,编译器拒绝了代码:
let c = g square
^^^^^^
Error: This expression has type ColouredShape.yellow ColouredShape.t
but an expression was expected of type
ColouredShape.red ColouredShape.t
Type ColouredShape.yellow is not compatible with type
ColouredShape.red
这个答案是在 glennsl 的帮助下写的: