Asking for the correct way to use Core module with functor and `sexp`

I have this code that works.

open Core

module Int = struct
  module T = struct
    type t = int [@@deriving compare, sexp]
  end

  include T
  include Comparator.Make (T)
end

type m = string Map.M(Int).t [@@deriving compare, sexp]

The least signature required for type t = int on the rwo book is [@@deriving compare, sexp_of] but the code doesn’t complain yet.

Now I need to put module Int into a functor and it cannot compile.

module Case_sexp = struct
  module Int = struct
    module T = struct
      type t = int [@@deriving compare, sexp]
    end

    include T
    include Comparator.Make (T)
  end

  type m1 = string Map.M(Int).t [@@deriving compare, sexp]

  module Make (S : module type of Int) = struct
    (* Error: Signature mismatch here for `S` *)
    type m2 = string Map.M(S).t [@@deriving compare, sexp]
  end
end

Question 1: I don’t understand here why m1 is fine but the compiler reports error at m2.

As a comparison, the code works if I follow the textbook using sexp_of instead of sexp

module Case_sexp_of = struct
  module Int = struct
    module T = struct
      type t = int [@@deriving compare, sexp_of]
    end

    include T
    include Comparator.Make (T)
  end

  type m1 = string Map.M(Int).t [@@deriving compare, sexp_of]

  module Make (S : module type of Int) = struct
    type m2 = string Map.M(S).t [@@deriving compare, sexp_of]
  end
end

Question 2: what is the suggested way to write the signature for a core-style module with comparator a.k.a. T or with comparable. module type of Int doesn’t look so nice to me but I don’t figure out the better way.

2 Likes

OK, this is the working code using Comparator.S.

module Case_sexp_working = struct
  module My_Int = struct
    module T = struct
      type t = int [@@deriving compare, sexp]
    end

    include T
    include Comparator.Make (T)
  end

  type m = string Map.M(My_Int).t [@@deriving compare, sexp]

  module type TS = sig
    include module type of My_Int
    include Comparator.S with type t = t
  end

  module Make (S : TS) = struct
    type m = string Map.M(S).t [@@deriving compare, sexp]
  end
end

For the original code, the difference between Int (without signatures) and S : module type of Int may only be related to type constraints.

btw, it’s kind of hilarious that I asked a similar simpler question 4 years ago. I finally get used to putting more things in functors anyway.

I am still thinking if it’s possible to write the module interface on the fly e.g. Make (S : <on-the-fly>) instead of explicitly defining TS here.

If I understand correctly, you are being bitten by the difference between module type of M and module type of struct include M end. The crucial difference in uses like your example is that the second form results in signatures that are strengthened by equations between the resulting signature’s types and modules and those in the original module M. So if you replace Make (S : module type of Int) by Make (S : module type of struct include Int end) in the first post’s code, it will compile.

When trying to understand this sort of issue, it is helpful to observe the signature that results from module type of using ocamlc -i or merlin, etc.

3 Likes

Thank you so much! Now I see the point.

module type of Int differs from module type of struct include Int end at the inner module type for Int.T, where

  • module type of Int contains a signature for the type of Int.T
  • module type of struct include Int end has a signature of that Int.T.

For the sake of the audience, I make a simpler code foo.ml

open Core

module Int = struct
  module T = struct
    type t = int [@@deriving compare, sexp]
  end

  include T
  include Comparator.Make (T)
end

module Make (S : module type of Int) = struct
  (* Error: Signature mismatch here for `S` *)
  (* type m = string Map.M(S).t [@@deriving compare, sexp] *)
end

module Make1 (S : module type of struct include Int end) =
struct
  type m = string Map.M(S).t [@@deriving compare, sexp]
end

module Make2 (S : module type of Int with module T := Int.T) = struct
  type m = string Map.M(S).t [@@deriving compare, sexp]
end

and dump the result via ocamlfind ocamlc -package core -package ppx_jane foo.ml > debug.ml. I trimed the result type of functors for simplicity.

module Int : sig module T ... end

module Make :
  functor
    (S : sig
           module T :
             sig
               type t = int
               val compare : t -> t -> int
               val t_of_sexp : Sexplib0.Sexp.t -> t
               val sexp_of_t : t -> Sexplib0.Sexp.t
             end
           type t = int
           val compare : t -> t -> int
           val t_of_sexp : Sexplib0.Sexp.t -> t
           val sexp_of_t : t -> Sexplib0.Sexp.t
           type comparator_witness =
               Base.Comparator.Make(T).comparator_witness
           val comparator :
             (T.t, comparator_witness) Core.Comparator.comparator
         end)
    -> sig end
module Make1 :
  functor
    (S : sig
           module T = Int.T
           type t = int
           val compare : t -> t -> int
           val t_of_sexp : Sexplib0.Sexp.t -> t
           val sexp_of_t : t -> Sexplib0.Sexp.t
           type comparator_witness =
               Base.Comparator.Make(T).comparator_witness
           val comparator :
             (T.t, comparator_witness) Core.Comparator.comparator
         end)
    ->
    sig ... end
module Make2 :
  functor
    (S : sig
           type t = int
           val compare : t -> t -> int
           val t_of_sexp : Sexplib0.Sexp.t -> t
           val sexp_of_t : t -> Sexplib0.Sexp.t
           type comparator_witness =
               Base.Comparator.Make(Int.T).comparator_witness
           val comparator :
             (Int.T.t, comparator_witness) Core.Comparator.comparator
         end)
    ->
    sig ... end

It’s obvious now

  • In Make, the sig of S has a module type T that is standalone.
  • In Make1, the module T equals to Int.T.
  • In Make2, the code also works. It seems with module T := Int.T only reflects in the constraints but no definitions.
6 Likes