I think that this cuts to one of the root causes why config scripts are used so much.
So to be clear, my opinion is that config scripts are way overused, and contain a lot of stuff that doesn’t belong there. I consider them a pile of bloat arbitrarily thrown together, and I think that they often make the code more difficult to understand.
IMO, there are a couple of reasons why so much data ends up in config scripts:
- Some of our flagship libraries (Phoenix and Ecto) promote it.
- Some libraries require it.
- There’s no obvious or convenient way to specify variations between different mix envs.
To be completely honest, at my company we also overuse config scripts, precisely for the reasons stated above. So for example, even though we’re aware of
if Mix.env() and how/when we can use it, if a constant varies between different envs, we usually just stuff it to config script.
I further believe that this convenience subtly creates a tendency to put even more data into config scripts. So for example, my colleague recently argued that some MFAs belong to config scripts, because they “feel” like config, even though MFA is clearly code, not config. This was for me the direct motivation to write that lengthy post about config scripts, and to actively start questioning them.
To be clear, I used them myself a lot, even though I was never quite comfortable with them, which is why I’m increasingly starting to feel that they are misleading people and causing problems much more then they actually help because:
- Config scripts are not runtime friendly, and cause confusion when used with OTP releases.
- Parameters are not consolidate anymore. For example, endpoint parameters are now specified in at least four config files.
- A bunch of unrelated data is thrown together.
Therefore, I feel that instead of adding the additional complex machinery to address the issue number 1 (which is just one issue of config scripts), it would be much better if runtime configuration through regular code was promoted and assisted.
In particular, I’d like to see:
- Official helper macros to simplify expressing small variations between mix envs in regular code. Perhaps something along the lines of this macro, or something different/better.
mix phx.new, Ecto/Phoenix docs, Elixir docs, and official getting started guides favouring runtime configuration over config scripts.
This will not solve all the problems, but I believe it will solve most of them, and that it will guide the community to write their code and libraries in a better way.
Now, some members from Elixir and Phoenix core team have mentioned that many people were further confused when
init/2 callbacks were introduced. Personally, I don’t at all buy that this means that runtime config is confusing. The thing is that prior to
init/2 a typical Elixir project had at least four files where parameters were specified (config, dev, test, and prod.exs). And then the fifth one was added. No wonder that people found this even more confusing.
But until we guide people to provide their parameters as much as possible in the regular code, it’s unfair to say that runtime config is confusing.
I should also state that I was not a fan of
init/2 when it was proposed. Personally, I felt that endpoint and repo should just take their parameters as argument to
child_spec/1. This has issues with hot code reloading, but that’s an advanced scenario anyway. My impression is that with
init/2, Elixir/Ecto/Phoenix team decided to make the runtime configuration more complex to simplify advanced (and arguably infrequent) scenarios at the expense of more complex interface for everyone. My feeling is that this was not a good tradeoff. I suspect that the callback style interface of
init/2 also adds to the confusion people have.
In summary, I think that Elixir/Phoenix/Ecto team historically favoured config scripts way more than runtime configs in regular code, and that’s why we’re here. Perhaps, instead of adding more complex machinery to config scripts, a better way would be to assist and promote runtime configuration through regular code and plain old passing of arguments to functions. I feel that this requires much less interventions in the language and that it can take us very far, although it will admittedly take time to move the community to such style of configuration.