It's an antipattern the inexperienced frequently fall into. Just one little extra bit of functionality, and ... whoops! Suddenly, you can program there, so now you will have to program there.
Same applies to markup languages. TimBL held HTML back from Turing-completeness for ages, then JavaScript came along.
> Just one little extra bit of functionality, and ... whoops! Suddenly, you can program there, so now you will have to program there.
That's an argument for programming languages in configuration, not against. It's just that we need good languages there, so that if you "have to program there" it's not a painstaking experience.
Designing a programming language is hard. Really, really hard. Or rather it's extremely easy to make completely unusable language, especially if it wasn't designed as a language from the start. You don't want to do this to your users and to yourself.
You either just don't include a programmable configuration, which is a fine tradeoff, or you include a "real" language from the start. For scripting languages it's easy, just use themselves. For compiled languages it's even easier: just use Lua or Scheme and be done with it.
But please, whatever you do, don't invent your own configuration (what's wrong with standard "key = value" format anyway?) format and if you do DON'T try to gradually extend it into a language. It will be hideous, unusable, insecure, unreadable, impossible to debug, buggy like hell and will haunt you forever in nightmares.
That being said, don't be afraid to experiment and try to build a language if you want. Just don't force it on others in production environment until you're sure it's not as described above :)
> ... whoops! Suddenly, you can program [in the
> configuration files], so now you will have
> to program there.
What's forcing a user to configure a system if they don't need to make changes? Why is it preferable to use something functionally restricted rather than a programming language to configure a system?
I'm still trying to figure it all out myself, but it feels like the line is drawn around the time that you find that the order of configuration options starts to matter, and probably also when start conditionally loading code based on it.
So it's something like the divide between "let's keep this in a variable because we might need to reference later", and "let's store this in a variable because we need to alter control flow based on it".
Hopefully I will know how to tell the difference someday.
> the order of configuration options starts to matter
Commutativity (and idempotency) are too important traits of declarative languages and they are at odds with turing-completeness: I have the feeling a lang can't be declarative turing-complete and efficient (finish in a reasonable time) at the same time.
So you pick two: efficient and declarative or efficient and turing-complete. Once you go turing complete anyway you can't guarantee termination and reasoning about programs becomes hard.
I'll take a stab at it, please feel free to critique.
All my reasons come down to complexity. Competing against our righteous need to make software do cool stuff, is the fact that everyone writes software that breaks all the time. Configuration that can perform arbitrary computation can put our programs into literally any state, making it that much harder to make them robust and correct.
It also opens up the risk that people won't configure their software correctly because they don't understand the configuration. And it even opens up attack vectors - what if there's a buffer overflow in your configuration interpreter, or a resource link that lets configuration files do arbitrary things to a system?
But I think one of the biggest risks is fragility. Configuration files that can do arbitrary computation will be made to do so. Software that gets used by lots of people will end up with towers of complexity built into the configuration, to the point that removing or refactoring them risks bugs, edge cases, or breaking a particular feature or misfeature that someone relies on. Better to control the complexity in the first place.
> Configuration that can perform arbitrary computation can
> put our programs into literally any state...
Considering that it's generally a poor design that expose the full state of an application to all parts of the same application (think: "global state"), then yes, it holds that exposing the entire application state to the configuration system is also a very poor design. I would think that if a user could set three values via configuration, then those three values would be the only state exposed.
> It also opens up the risk that people won't configure
> their software correctly...
We can always find someone who can't configure the system, regardless of how the configuration is done. If we're worried about making configuration "safer", then the system should be more careful about what configuration values it accepts i.e. implement robust validation.
> But I think one of the biggest risks is fragility.
Systems that can't adapt to unanticipated needs of their users get replaced by systems that can adapt. It's why many pieces of mature software develop this robust configuration ability (often hiding under the label "api", "scripting interface", "plugin framework", etc.)
> Software that gets used by lots of people will end up with
> towers of complexity built into the configuration, to the
> point that removing or refactoring them risks bugs, edge
> cases, or breaking a particular feature or misfeature that
> someone relies on.
This is not a inevitable. Many mature systems have successfully incorporated fully programatic configuration [1] without turning into "towers of complexity". (It probably says something about the application's architecture if it's so tightly coupled to it's configuration.)
You don't really have to worry about any of this if you use a proper interpreter for your configuration files (for example, if you do configuration by embedding Lua in your application, which is very common and useful). If you try to build up a programming language from scratch, you're right that you're going to have problems, but embedding a battle-tested, safe language means you don't have to worry about your program being put in "literally any state" or your program being susceptible to buffer overflows.
Django embraces programming in the config files. They are pretty much all Python code. Means you can do some fairly cool stuff. It ends up making a lot more sense to configure a Python framework in Python, rather than XML or something else.
Using DSLs for configuration can be incredibly useful: allowing a single "config file" to respond to its environment and work differently in different contexts.
Of course at some point, you're just calling your program your config file. But don't tell Emacs users that config files can't be turing-complete.