Basic Module and Functors question

Hi all,

In the process of learning OCaml while providing something useful, I’m trying to functorize a lib for ploting Kicad electronics schematics while remove a dependency to Lwt. But, it fails miserably. In the setup there’s a painter module that is able to paint primitive shapes to a canvas (here, for SVG output). Then this painter is used by the schematic painter module. I’d like to strip the Lwt_io dependency from the schematic painter while still providing it in the SVG painter to write the file.

module type Painter = sig

  (** the canvas of the painter *)
  type t

  (** [get_context ()] @return a new canvas *)
  val get_context: unit -> t

 (** here the primitive painting functions accepting t**)

(** A module able to paint a schematic in a schematic context *)
module type SchPainter = sig
  (** the schematic context *)
  type schContext

  (** the underlying context **)
  type painterContext

  (** [initial_context ()] @return an new empty context *)
  val initial_context : unit -> schContext

  (** Here some schematic primitive management **)

  (** [output_context context] retrieve the canvas from the [context] **)
  val output_context: schContext -> painterContext


module MakeSchPainter (P: Painter): (SchPainter with type painterContext := P.t) =

(** implement SchPainter **)

module SvgPainter =

  (** implement Painter signature **)

  let write (oc:Lwt_io.output_channel) t = (** code to write the picture to a file **)


Finally in the main code

module SvgSchPainter = MakeSchPainter(SvgPainter)
open SvgSchPainter

let () =
  ctxt = initial_context () in
  ... (** ctxt is added content and is now end_context **)
  SvgPainter.write oc (output_context endcontext)

This last line does not compile with the error “This expression has type painterContext but an expression was expected of type SvgPainter.t”. output_context has indeed the signature schContext->painterContext in spite of the with clause ni MakeSchPainter.

How do I make the public types match so that I can use the functions specific to SvgPainter on them?

1 Like

Your code mockup seems to work perfectly fine. In particular the signature of the functor MakeSchPainter should expand to

functor (P : Painter) ->
    type schContext
    val initial_context : unit -> schContext
    val output_context : schContext -> P.t

since the destructive substitution in the result signature SchPainter with type painterContext := P.t would have erased the very existence of the type painterContext.

Consequently, it seems plausible that the problem stems from somewhere else in your code.

This is puzzling. I’ve been trying to come up with minimal examples to reproduce this but I can’t.

module type S = sig
    type t
    val const : t
    val incr : t -> t

module Make(X: S): (S with type t := X.t) = struct
    type t = X.t
    let const = X.incr X.const
    let incr = X.incr

module X1 : S = struct
    type t = int
    let const = 57
    let incr = (+) 1

module X2 = Make(X1);;

X1.incr X2.const;;
- : X1.t = <abstr>
X2.incr X1.const;;
- : X1.t = <abstr>

Thanks for trying to reproduce.

The strange thing is that looking at the inferred signature of MakeSchPainter, output_context is as expected:

val output_context: schContext -> P.t

But when it is instanciated for real, the resulting module loses the type imposed by destructive substitution.

Merlin gives a strange type to output_context in SvgSchPainter:

val output_context: schContext ->painterContext®nd

I don’t know what this means.

If you’re still interested, I pushed the real project here

Thanks for sharing the repo, makes it much easier to debug. I think I’ve identified the issue; you’ve not exported the required type signature for the module here.

Your export currently looks like:

module MakeSchPainter(P: KicadSch_sigs.Painter): KicadSch_sigs.SchPainter

whereas it should look like

module MakeSchPainter(P: KicadSch_sigs.Painter): (KicadSch_sigs.SchPainter with type painterContext := P.t)

You’re using the signature with substitution while defining the module but you’re not exporting it on the interface (in the .mli file), so other external modules have no way of knowing that you actually made that substitution.

Fixing that removes the type error but gives a build error.

File "src/", line 8, characters 2-27:
Warning 34: unused type painterContext.
File "src/", line 1:
Error: Some fatal warnings were triggered (1 occurrences)

This can be fixed by erasing this line

   type painterContext = P.t

as that type isn’t being used. And now the code compiles. :tada:

Thank you very much for debugging my code :blush: ! I had forgotten this mli file that is useless now.

Just as a learning take away from this, how did you debug the code? Pure scrutiny or could you trail the instance to the mli file with the help of the development tools?

Thought process:

  • Why does Merlin think this is of type painterContext?
  • Looked at the source code in the .ml file - that seems fine.
  • Looked at the .mli file just to make sure nothing funny is going on in between - aha, there it is!

For the second one, I just guessed that if the type is being set already via :=, maybe just commenting out the repeated definition might do the trick, and it worked.