问题
Given two type declarations, I want to use a subtype of one type declaration in another. For instance, say I have colours of Red, Blue, Yellow, how would I specifically reference each subtype when making another type? This example isn't specific to my problem, but it's a simplification of the problem I'm facing. I've tried the below example, straight up referencing Red, etc. I've also tried Red of colour i.e:
type colour =
| Red
| Blue
| Yellow
type shape =
| Rectangle * Red
| Square * Yellow
Notice above how I'm trying to force a colour type of Red for the rectangle and a colour type of Yellow for the square, how would I go about doing this?
回答1:
If you really wanted to limit Rectangle
and Square
to just one color, you wouldn't need to represent the color--it would be redundant. But I assume you're asking a more general question than this.
OCaml doesn't support subtyping for this type of variant. You can't make a new type that just has Red
as its possible values, or just Red
and Yellow
.
However, subtyping is supported for so-called "polymorphic variants". You can have something like this:
# 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
Note that OCaml doesn't automatically infer subtyping relations. You will need to ask for them explicitly using the :>
notation.
In my experience, polymorphic variants add a lot of complexity to your code. So I would suggest using them only if they really make things better in other ways.
(I would also add that your type colour
is more or less exactly the same as an enum in C or Java. So it's not completely clear what you're asking. There's no way in C or Java either to create a new type that has just a few selected values from an enum.)
回答2:
In OCaml, one way to restrict a set of values is to do so via the module system. A module is a collection of type definitions, named values, and submodules. Each module exposes an interface, which is what would be used to guarantee the well-formedness of a value.
In the example here, we'd have a Rectangle
module that would provide a create
function letting you only create red rectangles. We'd also have a Square
module which similarly would only let the user create yellow squares.
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
Now we have rectangles and squares, each with their own constraints. A shape is either a red rectangle or a yellow square. This is guaranteed by the module interface Shape
which happens to be shared by both Rectangle
and Square
modules (but doesn't have to). This module interface forces you to use the create
function to create an object of type t
. Note that Rectangle.t
and Square.t
are different types which cannot be used interchangeably, even though the modules Rectangle
and Square
have the same interface.
If you're not lost at this point and want to go further with this solution, I recommend looking into the private
keyword which allows exposing type details in a read-only fashion.
回答3:
Here is an example with phantom types :
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)
as expected, the compiler rejects the code :
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
This answer was written with the help of glennsl : Apparently invalid phantom type in OCaml accepted by the compiler
来源:https://stackoverflow.com/questions/60532703/ocaml-selecting-a-types-subtype-in-another-type-declaration