In the life of every successful MPS project, there seems to come a point where it has to deal with cyclic dependencies. These are dependencies between modules, such as a language depending on a solution which refers to concepts defined in the language, or a language using another language which indirectly extends the original language, or some other form of convoluted dependency graphs that begin to cause problems for the developers working on the project.

Why avoid cyclic dependencies

Apart from “soft” reasons such as being confusing and making it more difficult to understand the project structure, cyclic dependencies cause several practical problems when building MPS projects.

First, there is the performance aspect. MPS has to build modules that form cyclic dependencies together in one “chunk”. This means that every time there is a change in one of the modules, all of them will have to be rebuilt.

Second, occasionally the dependencies in the cycle have to be generated in a certain order. For example, a language may have to be generated after the language that it extends due to some aspects using cross-model generation and requiring checkpoints of the extended language to be available. MPS tries to accommodate this and order dependencies in a certain way but it may fail to produce a buildable order.

Third, if your dependencies are forming a cycle that requires you to specify the bootstrap flag in your build project, this is a sign that you are going to have problems building your project in the future. In order to build projects that contain bootstrap cycles, it may be necessary to add the generated sources to source control. It is then easy for these sources to get out of date and this introduces an additional source of friction to the development process.

How to avoid cyclic dependencies

On the day-to-day level, cyclic dependencies often occur when a language grows large and internal logic is extracted into a separate solution (often named generically “helper” or “util”). Care must be taken to ensure that this solution does not depend on the language. A helper solution for a language must not refer to or use any of the concepts defined in the language.

Note that such a solution does not need any special designation. It does not have to be a plugin solution nor a runtime solution, a plain solution with no special settings is enough.

Another source of cyclic dependencies that I have seen is splitting large languages according to “topics”. Let’s say, your domain is describing rules and levels of adventure games. As it grows large, you may decide to split the game language into a language for describing spells and another language for describing monsters.

However, although such topic-based split may look natural and logical (after all, we create a separate language for each domain), it will easily lead to circular dependencies. Let’s say, you need to give the user a possibility to describe how a spell affects certain monsters. This leads to a dependency from lang.spells to lang.monsters. Now, let’s say the user wants to describe that certain monsters may use certain spells during combat. This requires a dependency from lang.monsters to lang.spells.

In order to avoid such dependencies, it is important to create a core language which defines the domain objects in the game that need to know about each other. In our case, this language would contain both Monster and Spell. Additional languages may then be created to define additional features, such as complex rules about how spells affect monsters, or conditions for monsters to use spells.

Of course, this problem may also be solved differently with more levels of indirections or abstractions, such as only defining some sort of Rule class in the core language which would affect RuleObjects, and then have Monster and Spell contain Rules which affect RuleObjects, or something in that direction. But this only proves the point: the core language remains, only now it defines Rules and RuleObjects.

Therefore: on a higher level, organize your projects as “core and extensions” where the core defines interfaces, abstract concepts, or extension points for extensions to implement or extend.

When cyclic dependencies do not matter

Cyclic references, especially in user models, do not need to be problematic per se. As a real-life example, we have Wikipedia articles which may refer to each other and form cycles, and this is of course a valuable feature. Notice, however, that I used the word references here. In fact, the problem starts when references turn into dependencies, usually for technical reasons, such as needing to generate and compile Java code.

If all you do with user models is generate some sort of non-executable output such as XML or JSON or HTML, the cyclic references do not need to be a problem.