Ocaml selecting a type's subtype in another type declaration

雨燕双飞 提交于 2020-04-18 06:54:24

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!