There is no such thing as an imperative or declarative "flow". By its very definition, declarative does not have "flow". It is just a statement. Imperative (in programming) literally means "describing steps that change state". Declarative (in programming) literally means "describing a state which is desired".
Declarative would be "I want a cheeseburger." Imperative would be "Get me a bun, and some lettuce, and tomato, and mayo, and raw meat. Cook the meat on a grill at high heat, flipping half way. Put the mayo on the bottom of the bun, then the meat, then the lettuce, then the tomato, then put on the top of the bun. Give it to me."
It's still strange to me how people learn about "magic words" like declarative and imperative, and then try to ssstttrrreeeeettttttcccccccchhhhhhhhhhh their meaning into some new paradigm that they have just thought up.
There is no such thing as an imperative configuration file. A configuration file describes how a program should be configured. Even when the configuration file "describes a series of steps to change state", it's still declarative, because the configuration file is still declaring to the program how to operate. It's the program that is imperative or declarative, depending on how it interprets and acts on the configuration file. (This is made clearer in programs like Ansible, which, within the exact same configuration file, supports both declarative and imperative statements)
Before you say "what about template files! jinja2! go templates! hcl!", that is merely a DSL, which is no longer a configuration file; it is effectively a program in a crude programming language, interpreted by an interpreter (the program loading the file).
(edit: I agree with the author's point! But I suggest we stop using these terms 'declarative' and 'imperative', and instead say "let's write programs that are functional enough that we don't need to write configuration files that are mini-programs")
Not sure I buy that logic. With that reasoning, all code is declarative because it's "declaring to the interpreter/compiler" how to operate.
I think he's making the same point you did. In one case, the config file is laying out a sequence of steps like you did for how to make a burger (imperative). In the second case, the file just defines the end state (declarative).
Sure, you could say that the first case is declarative because the "end state" being defined is pipeline itself. But the point is that we're talking about how to get a burger, not define how to make one.
To give an example familiar to this forum: imagine we’re building a package. This can be set up imperatively with a shell script, or declaratively with GNU Make. Make will figure out which output files are missing or older than their input files, constituting a difference. It will figure out (topological sorting) an admissible path through computing a new up to date file from input files that are already up to date (re their own input files). Now that’s cool and all. But the same end result can be achieved way simpler if you can skip the diffing bit. If you assume you’re doing a clean build, you can order your build steps optimally in a shell script, skipping the now useless complexity. If there has been an abandoned rub that had some successful steps, your shell script will waste their results, but still end in a correct state.
Sort of! All code is also imperative, eventually, at the machine code level. This is a perfect example of how useless the whole "imperative vs declarative" distinction is. Nearly everything in a computer is both imperative and declarative, in some fashion, at some point.
These terms were not made to be some concrete and inviolable paradigm of computing. Some academics just wanted to tell people to write programs where you didn't have to spell out every instruction to the compiler, so they made this crude distinction. Things like a function called give_me_a_temporary_file(), rather than writing out an entire function yourself to create and return a temporary file. But both are executing steps imperatively under the hood. So we shouldn't make a big deal about these terms or programs that do more of one or the other.
The differences that I'm pointing out are 1) declarative does not describe a flow, the flow is under the hood; and 2) configuration files do not actually perform steps, they merely describe arbitrary data, and only the program interpreting the configuration can determine if the result is imperative or declarative. For some programs, the exact same configuration file may result in either imperative or declarative execution.
I sort of tried to do this with the Factorio terrain generation system[2]. The first phase is to run a Lua program, which is imperative, but the result is an immutable object representing the terrain generation configuration, which in turn includes functional expressions, since a map where everything is constant would be boring.
[1] I f&!@#^ing hate Gradle; it is my go-to example of thoughtlessly mashing paradigms together because you can, resulting in something that nobody I have ever met really has really been able to work with except by trial and error.
[2] See https://factorio.com/blog/post/fff-200 and https://togos.github.io/togos-example-noise-programs/
Well, yes. Those words are not as well defined as common usage implies. Their meaning is completely dependent on what you define as your basic operations.
People usually agree on some large spaces of incomplete definitions, so the terms aren't meaningless without context. But never everybody agree, and never most people agree on all the details.
Anyway, configuration files is one context where people have very diverse concepts of basic operations, and so most people almost never agree.
The idea between having a declarative configuration vs a traditional one is well understood in the context of a machine. In one case, you describe how you want the final machine to look like and let the system decides how to reach that states, in the other you yourself input how the state will be changed step by step to reach the final stage. Sure the declarative configuration really is a DSL and there is an interpreter somewhere turning it into a step by step list of actions but that’s invisible to the end user.
It’s not magic just abstracted. That’s the good old Wheeler aphorism: “ There is no problem in computer science that can't be solved using another level of indirection.”
> ssstttrrreeeeettttttcccccccchhhhhhhhhhh
apt
My Emacs config would like a word with you. :D
Develipera make fun of how project managers call anything Agile without understanding what it means, then go and commit the same sin themselves.
Declarative in practice usually means that an intelligent solver works backwards from a description of a final state
Make a file is imperative. There should be a file here is delarative.
Making a file may fail depending on implementation if it already exists. "This file should exist" likely will not because it will do whatever is needed, including nothing, to ensure constraints are met.
The best configuration file in my opinion, though, is to keep it trivial, and just have one factory config with very few options.
Need to log in and think you need to set a username and password? Linux has that already, lots of apps use the user accounts instead of their own nonsense layer or even directly use SSH as their transport.
Need to enable optional modules? Can you just make them enable and disable themselves on demand?
Network settings? I hope there's at least the option to just discover everything automatically.
Is there a reason it couldn’t be “Give me a cheeseburger”? This declarative camp narrative kills me sometimes.
> Before you say "what about template files! jinja2! go templates! hcl!", that is merely a DSL, which is no longer a configuration file; it is effectively a program in a crude programming language, interpreted by an interpreter (the program loading the file).
It will devolve into messy "imperative" code writing "declarative" "configuration"? or whatever using templates, so there's no point in being a purist about The One True Way.
...or procedural programs while we are at it.
I don't disagree with your argument, but the definition can be extended in this use case. If we assume the "configuration" of the software is the target destination. What you can configure imperatively perhaps is in contrast to that what roads to take or not and I believe the term fits reasonably well without it losing precision.
Clearly you haven't seen the gradle files I have been subjected to.
My babel.config.js would like to disagree.
Except that mayo should go on top.
But forget about the mayo...regardless of where one stands on proper mayo position there is a much bigger issue with the imperative cheeseburger instructions given: there was no cheese!
That said, declarative clearly has advantages when it can work. You probably still want a "break glass" way to see what the actual steps that will be taken are, but can get surprisingly far without that.
You can even prepare alternative routes ahead of time, if you want to speculate on conditions. Is a good idea to role play some of that ahead of the time, in any case.
The counter-argument is that declarative configs inevevitably sprout programming-like features - and if they don't someone will write code to generate them.
(disclaimer - in true HN fashion I haven't properly RTFA'd - dinner is nearly ready and I'm feeling bold)
Ha! See the make manual for examples.
Background reading: https://stackoverflow.com/questions/3480950/are-makefiles-tu...
Dealing with Webpack cured me of the idea that declarative is strictly superior. Configuring it is just endlessly consulting the documentation, and then the underlying code, and then cargo culting StackOverflow answers to coerce the black box to create the output I want.
Google maps works great for a set of origins to a set of departures for certain modes of travel. Once you get outside those bounds, it can fail in significant ways.
Thus the entire premise of the article thus seems to boil down to: If someone has already done the work, don't re-do it.
The problem with declarative configuration is that as the systems they manage become more complex, inevitably you leave the bounds of the solved problem and have to start solving it for yourself imperatively.
No, someone (as in a person) doesn't have to define the imperative process, that would be stupid. We have computers to compute things, including computing new processes. Oracle didn't hire a bunch of human query planners to manually construct every query plan, they made a query planner. Google didn't sit down a nation of navigators to plan out every route, they wrote a program (or likely a set of programs) that computes it on the fly.
Which is precisely the sort of imperative process I am referrencing...
You can declare only when someone else already has performed the imperative chore, i.e. written the source code, programmed.
Q.E.D.
Yes all code is eventually imperative if you go down low enough. But your code should be as declarative as possible for your user. For example, the business department write a ticket for me to build a new feature. They essentially declare some functionality. I then take the imperative steps to code that on the front or back end. To do this, I will likely need to use some tooling. As far as the tooling is concerned, I am now the user/business department, I declare what I want in the tooling and the tooling should make it for me. The developers of the tooling then write imperative code to handle that. Maybe they rely on tooling to do so, in which case they become the user and so on. The cycle repeats itself recursively until it reaches the machine code at the bottom.
You could even take it further if you like in that the hardware engineers behind the processors are now the developers carrying out the imperative functionality who in turn carry out their job using electronic measuring tools which makes them users etc.
Basically, everything should be declarative by default for the intended user of the application unless they have specifically requested finer grained control. The user has enough to deal with getting their particular task done, they shouldn’t have to navigate between layers building the tools that they need to get their job done. Unless he’s working on something unique that requires it, the tradesman doesn’t want to go to the DIY store and give them instructions to build a drill, he just wants to buy a drill that meets his requirements so he can get on with his job.
But I like to use the example of a Git repository where you've renamed and also changed some parts of a file. You commit the new state - was it a deletion and a brand new file, or a migration and modification of an existing resource? It's usually a non-issue because Git is smart, and files don't really care about their identity - but what if this was a database server and you need control over what happens when, and how things drain? Do you trust the system to choose the right path vs. "delete the database and make a new one?"
At some point you'll need imperative thinking to make sure your bases are covered, even if that is "we have a 3 phase rollout plan for different versions of the declarative config." And that's perfectly fine, in many cases. But it's important to never get into a cargo-cult level of "if it can't be done with one declarative change it shouldn't be done" - because then you'll either get stuck or light things on fire.
Showing a map printout while ignoring runtime state is declarative; executing directions step by step, adapted to the present state of the system, is imperative.
I’ll set aside my feelings about stretching meaning to say — if you are going to use technical words this way, don’t use them backwards.
Hey, an interrupted "make" can always restarted from where it left off without ever having to do a "make clean", so what could go wrong.
Both of these examples seem like they would be better fit with Lua-based configs. Scripts as configs give incredible amounts of power to the user without the programmer having to specify everything the user might want to do.
Examples:
Declarative: I want a PC with the following code installed: X, Y and Z. Imperative: First upgrade X, then install Y, then uninstall W, then install Y and Z.
Not really, I found it quite self-explanatory.
LOL, he got it backwards, "turn-by-turn directions" is the imperative flow. Can't expect much after reading that.