Adam Warski
3 min readJan 13, 2020


Compile-time verification a little bit overrated. In case you have instantruntime verification which can do the same and more — it’s not really worse. Though with distage you can do compile-time verifications too. We don’t promote that much because of some limitations and because instant runtime tests usually allow us to find problems faster. But we offer such an option.

Run-time verification is worse, as it’s better to get feedback sooner rather than later, but I understand that there are other features for which you are willing to trade this particular characteristic.

I agree it’s a great thing to have, but not a must-have.

Also by fact lazy is a poor man’s alternative to ahead of time object graph pruning (“garbage collection”). Unfortunately compiler cannot guarantee that your bunch of lazy fields will not explode on startup (with a stack overflow after a huge timeout for example). Compiler is not a final answer to all the problems and its guarantees come packaged with some issues and limitations.

Probably I’m not having the same problems as you do, as I can’t recall having to introduce something like your “GC”. Usually the JVM is good enough in determining which objects are still used and which are not :).

lazy vals are not perfect — agreed — but they are good enough most of the time. If there’s any initialization explosion that stack overflows — something is definitely wrong with my code. I want to see and fix that.

Again, I suspect you have perfectly good use cases for distage, however in the apps I’ve came across, a combination of constructors, traits, lazy vals and defs was all that was needed. This “plain Scala” setup is less powerful, but also the simpler (conceptually and from a tooling perspective) one to use.

distage itself is non-invasive and all the “non-basic” mechanisms are very limited to ModuleDef scope. Also the amount of rituals is usually significantly less than in case of, for example, manual wiring or MacWire.

Nowadays I mostly use constructors (without any macros), so let’s stick to that. Yes, there is a bit of ceremony that is required (part of which macwire was designed to remove), but that’s a price I’m willing to pay for the simplicity of the whole mechanism.

It’s a matter of a simple `if` in case we don’t speak about transitive dependencies and highly variable contexts. E.g. you have several roles which have their business logic layers and these layers use shared components (e.g. database and message queue drivers).

Yes and no. If this is getting complex, maybe it’s a signal that you should structure the application differently? If large portions of the object graph need to be replaced, then I’d try isolating that part and using composition (instead of cake-like inheritance) of the trait-modules to use either one or another.

There are configuration scenarios where a simple “if” is not enough, but then we also quite probably enter the domain of “business logic”, not simple service graph wiring. Again, I would try reifying this as code and isolating these variable parts as stand-alone modules.

But maybe a fuller example would show the real problem that you have in mind, and how distage allows fixing that.

I’m wondering how you define “valid”. From my point of view they both indeed “valid” but very different. While macwire/manual wiring provide you more compile-time guarantees, distage is able to solve problems of different order which are practically unaddressable with other approaches because of hidden exponential explosion behind them. So, both approaches are valid but I doubt you would choose manual wiring or MacWire in case you want to build a big modular application with OSGi level of flexibility. At the same time I wouldn’t choose distage in case I need to write a 100-LoC microservice without any configurability.

So you’re saying both are valid but distage is better ;). I’ll disagree here, and I would rather say that in most applications you don’t need OSGi level of flexibility (glossing over the fact that the main strenght of OSGi was supposed to be changing module implementations at runtime, if I remember correctly — a completely different problem). Just using vanilla Scala features is enough.



Adam Warski

Software engineer, Functional Programming and Scala enthusiast, SoftwareMill co-founder