How to fix the OCaml compile error when using type annotations with functors?

226 views Asked by At

I am new to OCaml and I am trying use functors. When I use the module type annotations with the functors, this results in a compile time error in my code.

When I remove the : Printable (from the module FromToString line) and the : ToString (from the module IntToString line) annotations, the following program compiles without error:

module type ToString =
sig
    type t
    val to_string: t -> string
end

module type Printable =
sig
    type t
    val print: t -> unit
end

module FromToString (S:ToString) : Printable  =
struct
    type t = S.t
    let print a = print_string ( S.to_string a) 
end

module IntToString : ToString =
struct
    type t = int
    let to_string = string_of_int
end

module PrintableInt = FromToString(IntToString)
let () = PrintableInt.print 3

However, when I add these annotations (as shown in the code) the compiler gives the following error:

File "Functor.ml", line 26, characters 28-29:
Error: This expression has type int but an expression was expected of type
         PrintableInt.t = FromToString(IntToString).t

How can I use these annotations without causing a compile error?

2

There are 2 answers

0
octachron On BEST ANSWER

The root issue is that your signature constraints make the resulting module far too opaque. When you constrain your functor result:

 module FromToString (S:ToString) : Printable = ...

you are making the type t an abstract type, which can only be consumed by the to_string function and never been produced. In other words, a module of type Printable is unusable by itself.

When starting with functor, it is very often useful to look at the module type inferred by the compiler for the resulting module. In the FromToString case, this is:

module FromToString (S:ToString) : sig
  type t = S.t
  val print: t -> unit
end = ...

You can see that the inferred module type of the result

 sig
   type t = S.t
   val print: t -> unit
 end

that it is quite similar to Printable except that now the type t is equal to the type t of the argument module S.

Thus, it is possible to reuse Printable to write the full module type of the result by adding a type equality with a with constraint:

  module FromToString (S:ToString): Printable with type t = S.t = struct
    type t = S.t
    let print a = print_string ( S.to_string a) 
  end

The same issue appears for IntToString and can be fixed in a similar way:

module IntToString : ToString with type t = int =
struct
    type t = int
    let to_string = string_of_int
end

Then the compiler error is gone:

module PrintableInt = FromToString(IntToString)
let () = PrintableInt.print 3
0
Joule On

One has to make the type t public, using the with notation:

module type ToString =
sig
    type t
    val to_string: t -> string
end

module type Printable =
sig
    type t
    val print: t -> unit
end

module FromToString (S:ToString) : Printable with type t = S.t =
struct
    type t = S.t
    let print a = print_string ( S.to_string a) 
end

module IntToString : ToString with type t =int  =
struct
    type t = int
    let to_string = string_of_int
end

module PrintableInt = FromToString(IntToString)
let () = PrintableInt.print 3