functional-programmingocamlfunctor

Module type semantics in OCaml


I am new to OCaml and I am struggling a bit with understanding how module types work.

module type I = sig
  type t
end

module EQ (M : I) = struct
  let equal (x : M.t) (y : M.t) = x = y
end

(* Explicitly type declaration *)
module A : I = struct
  type t = string
end

module EStr1 = EQ (A)

(* Error: This expression has type string but an expression was expected of type
         A.t *)
let _ = EStr1.equal "1" "0" |> string_of_bool |> print_endline

(* No type declaration *)
module B = struct
  type t = string
end

module EStr2 = EQ (B)

(* OK. Outputs false *)
let _ = EStr2.equal "1" "0" |> string_of_bool |> print_endline

In the code above i declared module type I, module A with explicit module type declaration and module B without module type declaration. EQ is functor that receives module of type I and returns module with equal method.

Example with module A result in compiling error, while another works as I expected. What is semantic difference between this examples?


Solution

  • The output from the toplevel interpreter is instructive.

    # module type I = sig
      type t
    end
    
    module EQ (M : I) = struct
      let equal (x : M.t) (y : M.t) = x = y
    end
    
    (* Explicitly type declaration *)
    module A : I = struct
      type t = string
    end
    
    module EStr1 = EQ (A);;
    module type I = sig type t end
    module EQ : functor (M : I) -> sig val equal : M.t -> M.t -> bool end
    module A : I
    module EStr1 : sig val equal : A.t -> A.t -> bool end
    

    Note the signature for the EStr1 module. EStr1.equal has type A.t -> A.t -> bool. Not type string -> string -> bool.

    Applying the I signature to A has restricted us from knowing what A.t is. It is an abstract type. You haven't done that with B, so EStr2.equal does have type string -> string -> bool.

    You can also explicitly expose this type information.

    # module A : I with type t = string = struct
      type t = string
    end;;
    module A : sig type t = string end
    # module C = EQ (A);;
    module C : sig val equal : string -> string -> bool end
    

    Once you understand how abstract types work, they are very useful.