Let's say I had a simple module MyFoo
that looks something like this
module MyFoo = struct
type t =
| A of string
| B of int
let to_string thing =
match thing with
| A str -> str
| B n -> string_of_int n
end
With this definition, it works great and as expected — I can do something like
let _ = MyFoo.A "";;
- : MyFoo.t = MyFoo.A ""
without any problems.
Now maybe I want to create a functor that consumes modules with this structure, so I define a module signature that describes generally what this looks like and call it BaseFoo
module type BaseFoo = sig
type t
val to_string : t -> string
end
If I redefine MyFoo
the same way but giving it this signature like
module MyFoo : BaseFoo = struct
type t =
| A of string
| B of int
let to_string thing =
match thing with
| A str -> str
| B n -> string_of_int n
end
I lose the precision of its type t
(is there a better way to describe what happens here?) — for example:
let _ = MyFoo.A "";;
Error: Unbound constructor MyFoo.A
What exactly is going on here and why does it happen? Is there a canonical way for dealing with this kind of problem (besides just leaving off the signature)?
I've tried manually including the signature and the specific type type definition too but get a different kind of error (this probably isn't the right approach).
module MyFoo : sig
include BaseFoo
type t = | A of string | B of int
end = struct
type t =
| A of string
| B of int
let to_string thing =
match thing with
| A str -> str
| B n -> string_of_int n
end
let _ = MyFoo.A "test";;
Error: Multiple definition of the type name t.
Names must be unique in a given structure or signature.
You don't need the signature
What is going on is pretty much what you describe: giving
MyFoo
theBaseFoo
signature in its definition restricts it to the signature.Why? Because this is what specifying a signature at this place is for. The canonical solution is to leave of the signature (usually, letting the signature definition next to the module definition will be clear enough for the reader).
Note that when you call
MyFoo
on your functor, the signature will be checked. My usual choice is to rely on that.A few workarounds
Given what you've tried, I guess this could be interesting to you:
The
with type t = t'
clause allows you to annotate a module signature to add type information to it. It is quite useful, especially when dealing with functors. See here for more information.MyFooHidden
may seem useless, but you can see it as a check that MyFoo has the right signature. You can still useMyFoo
however you want after all.MyFooWithType
is actually (a bit) less useful because if you change your signature to add a type you'd want exported, you'd need to add the export here too.Using include
As for your
include
try. Well, nice try! You were almost there:The
with type t := t'
is a bit different in that it doesn't perform an equality but a replacement. The typet
definition is removed from theBaseFoo
signature altogether and all instances are replaced with your ownt
, that way you don't get any double definition problem. See here for more details.As you point out, this is probably not the approach you want, as you no longer easily know that
MyFoo
is indeed aBaseFoo
.