I have a library where functors have proliferated. It was quite painful to functorize a module deep in the stack, because of types not being equal anymore in many places.
Therefore I wonder if there is a better way to organize the code so that it would be less verbose and less painful to evolve.
To illustrate, let’s imagine we have module types
AB that depends on
ABC that depends on
AB expose, and what should
ABC take as input ? (and expose itself, for functors further down the line) ?
One possibility is to always pass everything as arguments, with all necessary
with type constraints, and to expose as little as possible.
So you would have
module ABC (A : A) (B : B) (AB : AB with type a = A.t and type b = B.t) (C : C) = struct ... end
But once you have many arguments with many types, this can become very verbose, and it’s difficult to get all the right equalities.
Another possibility would be for
AB to re-expose
B as submodules and only access them through there, so that you don’t need to pass them as arguments. Then when necessary you would use
with module A = A which is less verbose if you have many types.
module AB (A : A) (B : B) = struct module A = A module B = B ... end module ABC (AB : AB) = struct module AB = AB ... end
Then when you need
A, you do
But I have the notion that this is not a great idea, as it’s easy to end up with multiple
As that are not equal.
Yet another possibility, if you don’t actually want to parameterize on
functor A B -> AB implementations,
is to only pass
B, and derive
AB in the body of the functor.
module ABC (A : A) (B : B) = struct module AB = AB (A) (B) ... end
This looks like it should work well, but I have also ended up with type equality problems that I don’t really understand…
So, what’s the best approach ? What rules should one follow to make it all fit together nicely ? Is that even possible or is any refactoring necessarily painful ?
Thanks a lot,