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**)
end
(** 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
end
module MakeSchPainter (P: Painter): (SchPainter with type painterContext := P.t) =
struct
(** implement SchPainter **)
....
end
module SvgPainter =
struct
(** implement Painter signature **)
let write (oc:Lwt_io.output_channel) t = (** code to write the picture to a file **)
end
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?
Your code mockup seems to work perfectly fine. In particular the signature of the functor MakeSchPainter should expand to
functor (P : Painter) ->
sig
type schContext
val initial_context : unit -> schContext
val output_context : schContext -> P.t
end
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
end
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
end
module X1 : S = struct
type t = int
let const = 57
let incr = (+) 1
end
module X2 = Make(X1);;
X1.incr X2.const;;
- : X1.t = <abstr>
X2.incr X1.const;;
- : X1.t = <abstr>
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.
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/kicadsch.ml", line 8, characters 2-27:
Warning 34: unused type painterContext.
File "src/kicadsch.ml", line 1:
Error: Some fatal warnings were triggered (1 occurrences)
Thank you very much for debugging my code ! 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?
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.