I’m reading Real World in Ocaml; and cannot understand the below code and description;

module type Interval_intf = sig
type t
type endpoint
val create : endpoint -> endpoint -> t
val is_empty : t -> bool
val contains : t -> endpoint -> bool
val intersect : t -> t -> t
end
module Make_interval(Endpoint : Comparable) : Interval_intf = struct
type endpoint = Endpoint.t
type t = | Interval of Endpoint.t * Endpoint.t
| Empty
...
end
module Int_interval = Make_interval(Int) ;;
(* the module type is as below *)
module Int_interval :
sig
type t = Make_interval(Base.Int).t
type endpoint = Make_interval(Base.Int).endpoint
val create : endpoint -> endpoint -> t
val is_empty : t -> bool
val contains : t -> endpoint -> bool
val intersect : t -> t -> t
end

In Make_interval already has endpoint = Endpoint.t; why the endpoint’s type still not be exposed?

Type annotation : Interval_intf of functor Make_interval and definition of module type Interval_intf together hide actual implementation of abstract type endpoint. You could try to change : Interval_intf to : Interval_intf with type endpoint := Endpoint.t (or something like that) and check the difference.

Yes, I just read the destructive substitution, I just wonder that what’s the semantic meaning of type endpoint = Endpoint.t in the functor definition even endpoint type will not get datatype of Endpoint.t and the result is type endpoint = Make_interval(Base.Int).endpoint.

The endpoint type isEndpoint.t; from within the functor you use it as such. From outside the functor though, it is made abstract by the : Interval_intf, so you cannot make any assumption about it. If you remove that annotation, it will be concrete again. So to answer your question, the semantics of it is that the implementation of create, is_empty, etc need to know about the actual value of endpoint.

Now why voluntarily make that type abstract to the outside world ? The idea behind such a functor is to write generic code that works for any interval. This code only knows about the Interval_intf module type and will work over any interval, possibly intervals that will be written in the future with concrete types for endpoint we couldn’t think of. Thus, it cannot make any assumption about the nature of endpoint, just that objects of that type exist and have the given set of operations.

We could write this function to determine if two endpoints are equal:

module Equal (I : Interval_intf) = struct
let endpoint_equal (lhs : I.endpoint) (rhs : I.endpoint) = I.is_empty (I.create lhs rhs)
end

Here endpoint_equal has no information about the concrete nature of I.endpoint, but can still reason about it in an abstract manner. This is what Real World Ocaml is illustrating here.