9 ms·
> 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 t
by phailhaus 4y ago
> 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
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.
- lrem 4y agoThis is an important concept in my work. From experience, there is a pretty simple litmus test. It is pretty well understood that the word “imperative" denotes a system where the user describes a series of steps. The system executes the steps, in the end result bringing its universe to a desired state. The user does not need to describe what that is to the system, but a proof of correctness would decide whether that has been reached. Now, a declarative system is one where the user describes the desired end state and invariants on the admissible intermediate states of the universe. The system needs to figure out the current state of the universe, find the difference and find an admissible path to eliminate the difference. The litmus test to discern the two types of systems is whether the system concerns itself with the difference as a primary concept. 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.
- pxc 4y ago> Now, a declarative system is one where the user describes the desired end state and invariants on the admissible intermediate states of the universe. The system needs to figure out the current state of the universe, find the difference and find an admissible path to eliminate the difference. The litmus test to discern the two types of systems is whether the system concerns itself with the difference as a primary concept. Whether the system charged with realizing desired states reasons about the current state is actually not relevant. Declarative systems work by generating the desired state 'from scratch', often with some kind of efficient cache. A declaratively styled configuration system which still works by transitioning the system between target states statefully is still subject to the same problems as imperative configurations, but they're abstracted away from your field of view, as long as things sort of work. A stateless approach, also typically (always?) configured in a declarative style, actually addresses these problems in a principled way. For an example, consider NixOS vs. Puppet, Chef, Ansible, etc.: https://www.domenkozar.com/2014/03/11/why-puppet-chef-ansible-arent-good-enough-and-we-can-do-better/ https://www.domenkozar.com/2014/03/11/why-puppet-chef-ansibl... I like that essay's term for the paradigmatic differences, rather than the merely stylistic differences between imperative and declarative configurations: convergent vs. isomorphic.
- tremon 4y agoA stateless approach [..] For example, consider NixOS vs. Puppet, Chef, Ansible What do you mean with "stateless" here, and which of those meet the stateless criteria? I have a hard time imagining any of those without state.
- pxc 4y agoNixOS has atomic transitions between independent configuration states. So for a simple example, when you uninstall a program from your declared NixOS config and request a switch of configurations, NixOS does not take into consideration your current configuration so that it can uninstall those removed packages. Instead, it generates a brand new filesystem tree from scratch which does not include those packages. Similarly, configuration files are never edited in place, even with a guarantee of idempotency; they're regenerated from scratch. Deployments of server applications do usually involve some inherent state, like the contents of a database or the fact of which services are running. In that respect, some deployment tools in the Nix ecosystem are not isomorphic. But the software management and package management components, for example, are declarative without involving any reasoning about the current state. It is in that sense that those components are 'stateless' in a way that the rule of thumb proposed in the GP is not.
- marcosdumay 4y ago> With that reasoning, all code is declarative because it's "declaring to the interpreter/compiler" how to operate. 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.
- naphatkrit 4y agoyep!
- 0xbadcafebee 4y ago> With that reasoning, all code is declarative because it's "declaring to the interpreter/compiler" how to operate. 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.
- tremon 4y agoI don't think I agree with that. To me, the imperative vs declarative distinction is about the outside interface, not how the instructions are executed internally. All code is also imperative, eventually, at the machine code level But such a reduction is not useful. That's the same as stating that all computers are analog at the electrical level, or that the Internet is a series of tubes at the physical level. Things like a function called give_me_a_temporary_file() This seems purely imperative to me. A declarative statement would be (exists temporary_file). But that is a bad example anyway, as I struggle to find a good use case for why the end state of an instruction set would be the existence of a temporary file. If your goal is to perform subsequent steps with that temporary file, you're working with an imperative interface regardless of how you write it down.
- phailhaus 4y ago> configuration files do not actually perform steps And I don't think he's saying they do either. In fact, I don't think the post gets into execution details at all! If you go through it again, he's only talking about ways of writing config files, not ways of running them. In one approach, the config defines the pipeline itself. In the second approach, the config defines your desired end state. The "imperative" vs "declarative" distinction is entirely dependent on what your goal is. If your goal is to write a very specific pipeline, then the former is also declarative! But the context of the article is in achieving some desired end state in CI. With that context in mind, the former is "imperative" and the latter is "declarative".
- TOGoS 4y agoRight. I think the important distinction is whether an imperative language is used to describe things that could be done purely declaratively, as is the case with Gradle[1]. I think this happens all too often because the oldschool declarative system has some edge case that it can't handle, so someone reinvents it with a fake DSL which is actually a dialect of an imperative language, and now you have the worst of both worlds. I've always thought that what we needed was a way to allow both declarative and functional information to be provided, but with a clearer separation between the two. e.g. a build configuration language based on this approach might allow one to specify either 'buildArtifactId: "xyz123"' or 'buildArtifactIdExpression: (functional expression to compute a build artifact ID goes here)'. 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 https://factorio.com/blog/post/fff-200 and https://togos.github.io/togos-example-noise-programs/ https://togos.github.io/togos-example-noise-programs/
- atomicnumber3 4y agoI find discussions like this ("html is/isn't a programming language" etc) kind of tiresome because eventually you end up at "a compiler is a file format converter". You have reached Truth but it ends up not being a terrifically interesting statement and everyone just kind of goes back to whatever they originally meant with whatever they said that started the argument.
- throwawaaarrgh 4y agoMost people don't know that YAML is not a configuration format, because they never learned the difference between a configuration format and a data format. Once you know the difference, it's clear they are very different things, and when to use which, which leads to very different outcomes. The more accurate people's words are, the clearer they can communicate their ideas. Better communication leads to better results.