Require that min, max, (-) is defined on 'a'

Starting out, we have:

module Rect  = struct
  type 'a t = {
    x0: 'a;
    y0: 'a;
    x1: 'a;
    y1: 'a;
  } [@@deriving sexp];;

Now, I want to be able to define generic functions

left: Rect.t -> 'a
bot: Rect.t -> 'a
width: Rect.t -> 'a
height: Rect.t -> 'a

this would require that we have (-), min, max defined on 'a.

How do I enforce this constraint ?

You could use a functor which requires a module implementing a signature that contains those 3 functions and a type. Similar to what is done with Set or Map.

Here is what I have so far:

module type My_Dim =
  sig
    type t [@@deriving sexp]
    val (+): t -> t -> t
    val (-): t -> t -> t
    val min: t -> t -> t
    val max: t -> t -> t
  end


module Rect (D: My_Dim)  = struct
  type t = {
    x0: D.t;
    y0: D.t;
    x1: D.t;
    y1: D.t;
  } [@@deriving sexp];;

  let min_x (r: t) = D.min r.x0 r.x1;;
  let min_y (r: t) = D.min r.y0 r.y1;;
  let width (r: t) = D.((max r.x0 r.x1) - (min r.x0 r.x1));;
  let height (r: t) = D.((max r.y0 r.y1) - (min r.y0 r.y1));;

  (*
  let map (fx: 'a -> 'b) (fy: 'a -> 'b) (r: 'a t ) : 'b t  =
    { x0 = fx r.x0;
      x1 = fx r.x1;
      y0 = fy r.y0;
      y1 = fy r.y1; }
      *)
end

I need some help with the commented out function. The idea I want to express is:

given D: My_Dim, D2: My_Dim, fx: D.t → D2.t, fy: D.t → D2.t, r: D Rect.t
we can produce a D2 Rect.t

Is the most elegant solution to this:

module type My_Dim =
  sig
    type t [@@deriving sexp]
    val (+): t -> t -> t
    val (-): t -> t -> t
    val min: t -> t -> t
    val max: t -> t -> t
  end


module Rect (D: My_Dim)  = struct
  type t = {
    x0: D.t;
    y0: D.t;
    x1: D.t;
    y1: D.t;
  } [@@deriving sexp];;

  let min_x (r: t) = D.min r.x0 r.x1;;
  let min_y (r: t) = D.min r.y0 r.y1;;
  let width (r: t) = D.((max r.x0 r.x1) - (min r.x0 r.x1));;
  let height (r: t) = D.((max r.y0 r.y1) - (min r.y0 r.y1));;
end
  
module RectConv (D1: My_Dim) (D2: My_Dim) = struct
  let map (fx: D1.t -> D2.t) (fy: D1.t -> D2.t) (r: Rect(D1).t ) : Rect(D2).t  =
    { x0 = fx r.x0;
      x1 = fx r.x1;
      y0 = fy r.y0;
      y1 = fy r.y1; }
end

This looks somewhat verbose for OCaml.

What you want is “modular implicits” (viz. Rust “traits”). Someday …

if you move a type out of the Rect functor then you can get rid of RectConv?

type 'a rect = {
  x0 : 'a;
  y0 : 'a;
  x1 : 'a;
  y1 : 'a;
}

module type My_Dim = sig
  type t
  val ( + ) : t -> t -> t
  val ( - ) : t -> t -> t
  val min : t -> t -> t
  val max : t -> t -> t
end

module Rect (D : My_Dim) = struct
  type t = D.t rect
  let min_x (r : t) = D.min r.x0 r.x1
  let min_y (r : t) = D.min r.y0 r.y1
  let width (r : t) = D.(max r.x0 r.x1 - min r.x0 r.x1)
  let height (r : t) = D.(max r.y0 r.y1 - min r.y0 r.y1)
end

let map fx fy r = { x0 = fx r.x0; x1 = fx r.x1; y0 = fy r.y0; y1 = fy r.y1 }

You can save a little horizontal space if you define a type alias (well not really but it’s slightly neater):

module type My_Dim = sig
  type t [@@deriving sexp]
  type binop = t -> t -> t

  val ( + ) : binop
  val ( - ) : binop
  val min : binop
  val max : binop
end

Another possibility:

type 'a t =
  {
    min: 'a -> 'a -> 'a;
    sub: 'a -> 'a -> 'a;
    x0: 'a;
    x1: 'a;
    y0: 'a;
    y1: 'a;
  }

(that is, store the functions that you need on 'a as part of the data).

Cheers,
Nicolas