dark_ruby
dark_ruby

Reputation: 7866

Module dependency cycle

I have:

Module 1:

Module 2:

Module 3:

question

as a result I obviously get dependency cycle: src/Module3.cmj -> src/Module2.cmj -> src/Module3.cmj error by compiler. Something that is trivially achievable in TypeScript/JS with individual import, is not possible in Reason. How to get around this?

I don't really want to change the architecture of my program, just to facilitate shortcomings of compiler/module system.

Upvotes: 3

Views: 2842

Answers (2)

PatJ
PatJ

Reputation: 6144

The simplest way to handle your problem is indeed recursive modules. I don't advise you to use them, as recursive modules can make your code harder to read, compile and can in the most complex cases break your code at run time. Not to mention if you use side-effects in your module definitions (please don't).

I will use the OCaml syntax, you should be able to easily translate to Reason.

If you want to go with that anyway, here is the quick and dirty solution, using recursive module and functors.

The quick and dirty solution

1) Create a module myModTypes that will indicate the expected types of module2 and module3. It should look like:

module type Module2type = sig ... end
module type Module3type = sig ... end

with ... being the expected signatures of your modules (if you already have interface files written, just copy/paste them here, if you don't write those, they are important)

2) Put module2 and module3 within functors expecting the other module

For example, the code of module2 should now look like

module MakeModule2(Module3 : MyModTypes.Module3type) = struct
(* the code of module2 *)
end

The code of module3 will be in the same way, just swap 2 and 3 in the added lines.

3) Create a module makemodules2and3 with that code (translated to Reason):

module rec Module2 : MyModTypes.Module2type = Module2.MakeModule2(Module3)
and Module3 : MyModTypes.Module3type = Module3.MakeModule3(Module2)

Note that recursive module definitions always expect a module type.

4) Subsequent uses of Module2 and Module3 should now open Makemodules2and3 before being able to use them.

The right solution

You have to change the architecture of your program. Slightly.

As the OP said, there are no cycle of dependency in the functions, and that's a relief. Just split module2 and module3 into two new modules each. One with the functions that only depend on module1 and their own module, one with the "next step" functions.

This is a better way to approach how you declare your modules: they should be one with the types they define. Ideally, you have a module for each type, plus one additional module for each interaction between types.

Upvotes: 5

Yawar
Yawar

Reputation: 11627

Looks like Module1 doesn't depend on the other two modules. You can keep it as is. But since the other two are mutually recursive, you can express that using the recursive module syntax. This does have a requirement though that you declare the module signatures at the point of definition, since Reason needs to know what to expect. For example:

/* Impl.re */
module rec Module2: {
  type type2;
  let make: (Module1.type1, Module3.type3) => type2;
} = {
  ... actual implementation of Module2 ...
} and Module3: {
  type type3;
  let make: Module1.type1 => type3;
  let foo: Module1.type1;
  let bar: Module2.type2 => type3;
} = {
  ... actual implementation of Module3 ...
};

This is the general shape you'd use, you can adapt it to your needs.

If you don't want your users to have to do Impl.Module2.... to access the recursive modules, you can even expose them as file modules using include:

/* Module2.re */
include Impl.Module2;

And you can even annotate the implementation modules (Impl.Module2 and 3) with a compile-time warning to let users know not to use those ones:

/* Impl.re */
[@deprecated "Please use Module2 instead of Impl.Module2"]
module Module2: {...} = {...};

Upvotes: 4

Related Questions