I think you can use ppx_import to do this. You define in a.mli, and in a.ml you [%import ...] the module type. When I saw that, I thought: āclever, hehā.
What is the best introduction document to ppx?
Sometimes, I feel I should use them, but some other times they remind me of camlp4/camlp5 and I prefer to not put my hands anywhere near those dangerous gears.
Well,
(1) thereās -using- PPX rewriters, and thereās -implementing- PPX rewriters.
(2) to -use- 'em, well, thatās pretty easy, and you can follow the instructions for 'em on their homepages. Itās pretty straightforward, to the point where truly, thereās not much need for an introduction to them generally. I mean, itās as simple as just adding ā-package ppx_importā to your compile lines, and
type t = [%import ...argle bargle gargle ...]
in your code. [Iām not writing something real b/c seriously, the ppx_import docs are not bad, and the testsuite is comprehensive and didactic.
Also, the -syntax- of PPX extensions/attributes is -fixed- in the camlp5 grammar, so in that sense itās not like camlp4/5.
One option is to not have the a.mli file at all because you donāt need it. I assume you actually want to make all three public:
ABC
DEF
Abc
In that case you just need the implementation file a.ml, you donāt need the redundant interface file a.mli. The implementation already exposes exactly what you want to expose.
Another non-PPX solution is to have a file a_intf.ml:
module type ABC = sig ... end
module type DEF = sig ... end
module type A = sig
module type ABC = ABC
module type DEF = DEF
module Abc (S : DEF) : ABC
end
Then in a.mli you can just write include A_intf.A, and in a.ml you can include A.
Unless the compiler has special support for this, I donāt think you can have an mli file with no corresponding ml file. Put differently, in a phrase like (S : Signature.DEF), āSignatureā refers to a module, but the signature.mli file only gives a module type. Edit: I am wrong about this.
So long as a.ml remains exactly as it is in this example thatās true, but the cost of having no explicit interface is that you canāt add anything at the top level of the module without exposing it.
Besides what matters to the compiler, there is also something to be said for having a reference where one can read the interface of a module without having to sift out the implementation details.
signature.mli:
module type ABC = sig
type t
end
module type DEF = sig
type t
end
impl.ml:
module Abc (S : Signature.DEF) : (Signature.ABC with type t = S.t) = struct
type t = S.t
end
test.ml:
module M = Impl.Abc(struct type t = int end)
Makefile:
all: test
signature.cmi: signature.mli
ocamlc -c signature.mli
impl.cmo: impl.ml signature.cmi
ocamlc -c impl.ml
test: test.ml impl.cmo
ocamlc -o test impl.cmo test.ml
Well in these kinds of situations the main goal is to define a functor, so all or most interesting stuff should go inside the input and output signatures and the body of the functor anyway. The rest of the file itself should be empty.
I think it is misunderstood how ā¦ useful macro-extensions can be. Perhaps the difficulty and opacity of writing PPX extensions is partially at fault. Certainly in writing any sort of AST-processing software, the opportunities for making code more succinct, maintainable, and -understandable- by using macros, are everywhere. I think a sign that PPX is healthy will be if many people write small one-off PPX rewriters to solve small problems in their packages, sort of like how many scheme programmers have small collections of macros they cooked-up to solve specific problems that bothered them.
This is interesting: dune does indeed appear to support this with the āmodules_without_implementationā field. I have not done it before, but the result seems to be that you can have two interface files, one for āDRYā purposes and one for ālimit the interfaceā purposes. This seems to work:
dune:
(executable
(modules_without_implementation a_dry)
(name test))
a_dry.mli:
module type ABC = sig
type t
end
module type DEF = sig
type t
end
a.mli:
module Abc : functor (S : A_dry.DEF) -> sig type t = S.t end
a.ml:
module Abc (S : A_dry.DEF) : (A_dry.ABC with type t = S.t) = struct
type t = S.t
end
test.ml:
module M = A.Abc(struct type t = int end)
I am less certain that this is worth the hassle though, and whether I would want to do it in real life. Putting the signatures of ABC and DEF in a.ml and repeating them in a.mli doesnāt seem that bad.