Settings

Theme

Imperative configs are out; Declarative configs are in

prodvana.io

136 points by ayf 3 years ago · 93 comments

Reader

0xbadcafebee 3 years ago

"My dad switched from working with a map printout without knowing real-time conditions, the imperative flow, to Google Maps with turn-by-turn directions, the declarative flow."

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")

  • phailhaus 3 years 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 3 years ago

      This 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 3 years 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-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.

        • joshuamorton 3 years ago

          > Declarative systems work by generating the desired state 'from scratch', often with some kind of efficient cache.

          This is an option, but it is certainly not a requirement. If I say "I want cheeseburger", it is irrelevant to me if the cheeseburger is made from scratch or had been partially assembled ahead of time.

          You are concerning yourself solely with configuration that can be generated not only declaratively, but also fully deterministically, but configuration systems can indeed exist where the environment isn't sufficiently deterministic. In such cases, taking the current state of the world, the user's request, and then determining the appropriate (and preferably minimal) actions to take to harmonize those without having to have them specified by the user is a declarative approach.

          For example, I can go to the AWS console and turn up a job with a set of parameters, or I can have a configuration file and a daemon and if

              {
                  cpu: 5,
                  mem: 20,
                  image: "my_docker_url",
               }
          
          is present in that file, I can then assume a job meeting those specifications exists, and if not present the job will not exist. The daemon might do this by shutting down all the jobs and starting new ones every time the file is parsed, but that is probably not great. Approaches that only restart changed jobs, or which can dynamically resize jobs that support such things might lead to significantly better performance of th esystem as a whole.
          • pxc 3 years ago

            I completely agree! I mistakenly ommitted the word 'can' from the quoted sentence. I just meant to offer another example of declarative configuration management. The style of configuration/automation (of a config management tool) can be declarative whether the implementation has to consider the current state of the system or not.

        • tremon 3 years ago

          A 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 3 years ago

            NixOS 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.

      • wruza 3 years ago

        This is also not declarative vs imperative. It is declarative with a functionalty vs imperative without it. A shell script could also check which parts are missing or outdated and do incremental build. I’m not any good at shell scripting so here is a similar pseudocode:

          import cc, ld, glob, make
        
          // make defined as:
          fn make(to, from, how) {
            if (exists(to) && mtime(from) <= mtime(to) {
              return
            }
            how(to, from)
          }
        
          make("foo.o", "foo.c", cc)
          …
          make("a.out", glob("*.o"), ld)
        
        It’s basically the same as Makefile, except that out “make script” doesn’t know about goals anymore, only steps to take. Do we need that goal-knowledge in a declarative format is an open question, because you can wrap these make() calls in a function named aout() and that becomes your declarative goal. However declarative your config/data is, you still pass it to some evaluator like `make` at the end of the day.
        • KMag 3 years ago

          An important difference here is that your shell script executes the steps in the order you defined. Make performs a topological sort on the required rules, so in the general case, a Makefile doesn't explicitly define the ordering of the steps, but instead the make runtime figures out an order on its own based on the dependencies.

          The Makefile is a (default) goal and a sea of possible actions from which make creates a plan of action.

          The shell script steps are manually ordered by the programmer.

    • 0xbadcafebee 3 years 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.

      • jerf 3 years ago

        "All code is also imperative, eventually, at the machine code level."

        This is essentially what I think, and I've thought for a long time: https://news.ycombinator.com/item?id=3507281

        To the extent that people say "But what about..." my answer is that there isn't a particularly useful line to draw between imperative and declarative. There is one; I can draw it too. I challenge its usefulness. Imperative things have too many declarative things mixed in, and vice versa, in practice for it to be a very useful metric. I find what I mentioned in that post about the ease of debugging to be the real information I get when someone uses the "declarative" phrase; I can pretty much count on things breaking and me being unable to fix things whenever I see that word used.

        I find it much more useful to mix things up as appropriate and not sweat which things they happen to be. A "declarative style" is a useful tool to be used, little more, and it almost never belongs in any sort of pro or con list. The pros or the cons should be at the next level down, like, "it's hard to debug" or "I'm typing way too much for what I'm trying to accomplish". I haven't evaluated any techs and given or subtracted points merely for being or not being "declarative" in a long time.

      • phailhaus 3 years 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".

        • 0xbadcafebee 3 years ago

          Well, he does get into execution detais; he's showing you multiple configs that have a lot of steps in series, and saying, "Look, this file is ridiculous! Too many steps! Bugs! Instead, just define one function! Let the program deal with it!" Which I 1000% agree with.

          I get that the whole story is saying "do things declaratively". But I think that term, and its ability to be misused (as in the quoted example) are distracting, because we get lost in the weeds and miss the real point, which is that we shouldn't be writing pseudo-programs in configuration files. I think we can all agree on that; so let's just say that, and leave the magic words alone.

          • phailhaus 3 years ago

            Where's the execution detail? He never tells you how the config file is going to be run. Again, the entire post is relative to the goal of getting a stable CI build. If your config file is filled with implementation details of that goal, you're being imperative.

            It has nothing to do with "writing programs in your config file". The problem is that your config file is telling the runner how to reach the goal rather than what the goal is.

            • lstodd 3 years ago

              > The problem is that your config file is telling the runner how to reach the goal rather than what the goal is.

              The real-world alternative is a partial rewrite of your CI infra, which usually not feasible.

      • tremon 3 years ago

        I 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.

      • heavyset_go 3 years ago

        Python's setuptools configuration file via setup.py can perform steps, it's literally just a Python script.

    • TOGoS 3 years ago

      Right. 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 and https://togos.github.io/togos-example-noise-programs/

    • atomicnumber3 3 years ago

      I 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 3 years ago

        Most 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.

    • marcosdumay 3 years 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 3 years ago

      yep!

  • WastingMyTime89 3 years ago

    You are missing the forest for the trees by confusing the end result with its implementation.

    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.”

  • rising-sky 3 years ago

    I had the same thought when I read that paragraph and anecdotal tortured "lost with map printout" description, immediately thought, not sure this author knows quite what they are talking about, but kept reading to see what the configuration would look like...

    > ssstttrrreeeeettttttcccccccchhhhhhhhhhh

    apt

  • taeric 3 years ago

    > There is no such thing as an imperative configuration file

    My Emacs config would like a word with you. :D

  • ClumsyPilot 3 years ago

    > 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.

    Develipera make fun of how project managers call anything Agile without understanding what it means, then go and commit the same sin themselves.

  • eternityforest 3 years ago

    The common meaning of imperative means you tell a computer something step by step. If a config file does that, it's imperative.

    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.

  • wruza 3 years ago

    Declarative would be "I want a cheeseburger." Imperative would be "Get me a bun, and some lettuce, and tomato, and mayo, and raw

    Is there a reason it couldn’t be “Give me a cheeseburger”? This declarative camp narrative kills me sometimes.

  • 1letterunixname 3 years ago

    If felt as hand-waving as reading that "Clean Code" guy.

    > 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.

  • raxxorraxor 3 years ago

    > let's write programs that are functional enough that we don't need to write configuration files that are mini-programs

    ...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.

  • thfuran 3 years ago

    >There is no such thing as an imperative configuration file.

    Clearly you haven't seen the gradle files I have been subjected to.

  • eurasiantiger 3 years ago

    ”There is no such thing as an imperative configuration file.”

    My babel.config.js would like to disagree.

  • thriftwy 3 years ago

    Most Java configuration files are a series of method calls laid out in XML or YAML. These are clearly imperative, the bun-and-patty imperative.

  • sly010 3 years ago

    Indeed. Reading SICP should be a requirement for starting a YAML company.

  • geuis 3 years ago

    Very good breakdown. Couldn't agree more.

    Except that mayo should go on top.

    • tzs 3 years ago

      Alton Brown recommends mayo on the bottom to create a barrier to keep the bun from getting soggy from burger juices.

      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!

    • 0xbadcafebee 3 years ago

      I don't disagree, but a lot of burgers end up dry, so the bottom bun stays dry and bland, which makes me sad. Sauce on the bottom solves that, and the top bun gets wet from the tomato. Optimizing for the 80% burger.

      • jdminhbg 3 years ago

        The oil in the mayo acts as an aquaphobic barrier between the tomato and the bun. You don't want a "dry" bun but a soggy bun is structurally deficient on top of being bad tasting.

taeric 3 years ago

I.... don't buy the initial example. Google maps is still an imperative list of directions. It just keeps track of the current pointer for you, and is able to reroute. If anything, that is an example that a dynamic action plan is more adaptable than a static one.

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.

  • naphatkrit 3 years ago

    Oh totally that declarative can and will often compile down to imperative. The question is what do you ask the user for? My take is Google maps is declarative in the sense that you ask for your destination, your constraints (e.g. no highway), and time to leave, and it dynamically generates the underlying imperative steps like you said (but continuously adapt if you go off course, which you cannot do unless the initial input was declarative).

    • taeric 3 years ago

      That example still feels off. The "offline printout" was done in a very declarative way. It is just fixed on the execution plan. (And, quite frankly, less obnoxious to me if I decide to stop and get gas or food.)

      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.

      • everforward 3 years ago

        Declarative stuff gets converted to imperative at execution time. A significant difference is that a declarative system is not dependent on a particular state.

        Telling a site to get you from X to Y is declarative, because X can be anything and it works. The printed out instructions are imperative, and only work for a fixed X or somewhere already on the path.

        Maps is more declarative because it continually generates imperative instructions to get to Y regardless of where you currently are.

        • taeric 3 years ago

          Right, but the point is that when you are in the execution, you are back in the imperative. And with google maps, you are almost always in the execution side. Is why I said that would be a better example for dynamic plans being better than static ones.

          I'd go further and say that not just dynamic and static, but interactive versus non-interactive is the showcase there. Declarative/imperative is just not that applicable to that scenario.

          And again, I do like the rest of the article and the idea that is getting explored. I just question if that is really a great example for declarative/imperative. You have to squint to make it work, for whichever version you want to support.

      • naphatkrit 3 years ago

        That makes sense. I'm not necessarily arguing for continuous intelligence in this post, just that declarative configs enable it. Even the initial conversion of a destination to a set of directions is a compilation of declarative to imperative. Imagine if Google Maps was truly imperative and asks you to input the individual roads you want ahead of time - then it would not be useful.

        • taeric 3 years ago

          Apologies if it sounded like I didn't like the post. I think the rest of it was great and your point, I think, is conveyed well. I just got hung up on the lead in.

          Getting any directions from a system will be hard to turn into an imperative process, to be honest. It is imperatively telling you what to do, already. And, amusingly, you typically use an imperative to activate it. "Google, show me directions from here to ____." Literally an imperative voice cue. (I think you can twist this to be more "I want directions from here to there," such that it is not imperative, but this feels like it is stretching.)

          Declarative would be a bit more itinerary based. You fill in a few details of "On this day/time, I want bagels. On this day/time, I want to be at a hotel in the city. Etc." Then, it would output a list of steps on how you could make that work.

          And this ties it in nicely with infrastructure config. Especially at the beginning when you don't have pre-existing state of services, you can quite easily declare what you want. It is more when you have to also start defining migrations that you are likely to drop into imperative steps. (Really, any management of state transition will almost certainly be easier using imperative.)

        • shkkmo 3 years ago

          > Imagine if Google Maps was truly imperative and asks you to input the individual roads you want ahead of time - then it would not be useful.

          I'd love to be able to have the ability to request specific roads in Google Maps directions, as it is you have to jury-rig imperative elements by adding waypoints at the start and end of the route you want included.

          This illustrates me point: Systems for transforming declarations into imperative steps will always have limitations in terms of what can be declared and what states that can generate good imperative steps from/to. Once you step outside those limitations, you inevitably need to re-introduce some amount of your own imperative configuration. Ideally, eventually you make that imperative logic declaratively configurable and contribute that back to the commmunity.

andybak 3 years ago

This is an old debate surely? My first awareness of it was probably related to Django's settings.py back around 2007 but I got the feeling from discussions then that it was a discussion that had been bouncing around for a long time before that.

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)

  • nickcw 3 years ago

    > The counter-argument is that declarative configs inevevitably sprout programming-like features - and if they don't someone will write code to generate them.

    Ha! See the make manual for examples.

    Background reading: https://stackoverflow.com/questions/3480950/are-makefiles-tu...

  • naphatkrit 3 years ago

    Yep the idea of declarative winning out over imperative is nothing new. Yet for some reason, most deployment systems make you go through an imperative flow if you want to orchestrate releases.

    • taeric 3 years ago

      Because "burn it all down and rebuild everything" is by far the easiest orchestration to create from a declarative config. And is almost certainly not what any technician is going to want to happen to their production system.

      • scubbo 3 years ago

        Sort-of a nitpick, but for stateless services that's _precisely_ what you want to happen!

        • nitwit005 3 years ago

          The clients and network have state. If you kill every service, and set everything up from scratch, your users are probably seeing an error message. Their connections all died and the retries failed while you were spinning up the new version of things.

          • scubbo 3 years ago

            Right, which is why you set up the new versions of the service _first_ and cutover to them before tearing down.

        • taeric 3 years ago

          Not when your infrastructure includes your data store, though. Or if it includes such things as your application endpoint. You may want to stand up the new endpoints and then drain over traffic, as an example. Not take an outage for a deployment.

          • scubbo 3 years ago

            Right, yes - sorry, I should have clarified that I didn't mean "tear down then set up", but rather "set up (new), cut over, then tear down (old)". But still - the service should be "created from scratch"!

          • tremon 3 years ago

            Not when your infrastructure includes your data store, though

            Then the service is no longer stateless.

            • taeric 3 years ago

              I mean, you aren't wrong. But I am challenged to think of a service that doesn't store state somewhere.

              And you ignore the other situation. Even a fully "stateless" service that literally just responds to things probably doesn't want an outage just to deploy a new version.

              • tremon 3 years ago

                That's true. The way we solve it is by splitting our infrastructure deployments in two parts: a volatile part and a persistent part; the volatile part is versioned by buildnumber, so each deployment is side-by-side with the previous one. The drain/switchover is a manual process for now as we have a user acceptance step in the middle.

                It gives us more confidence when deploying the stateless part, but sadly it also means that the persistent deployments don't get as much exercise as the stateless part and are somewhat more prone to bitrot.

  • gtirloni 3 years ago

    The whole article is an ad.

rgovostes 3 years ago

As a university student discovering constraint solvers like Z3, I believed that the future of computing was declarative programming, with black box solvers that would obviate the need for specialized algorithms.

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.

  • thenipper 3 years ago

    There is a ton of interesting work being done with solvers adjacent to machine learning. I just finished a job where I was working with an operations research team solving inventory placement and transportation

shkkmo 3 years ago

Declarative works better than imperative only when someone has already defined the imperative process that takes you from any given state to your goal and does that for you based on your declaration.

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.

  • Jtsummers 3 years ago

    > Declarative works better than imperative only when someone has already defined the imperative process that takes you from any given state to your goal and does that for you based on your declaration. [emphasis added]

    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.

    • shkkmo 3 years ago

      > 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...

  • righttoolforjob 3 years ago

    This is the correct answer.

    You can declare only when someone else already has performed the imperative chore, i.e. written the source code, programmed.

    Q.E.D.

rcarr 3 years ago

My two cents on the declarative vs imperative debate:

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.

btown 3 years ago

Declarative is great if there is always one path to get to where you're going.

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.

zmgsabst 3 years ago

This example is backwards:

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.

kazinator 3 years ago

> If a pipeline fails halfway, most CD systems don’t have the ability to retry. The only option is to restart a pipeline from the very beginning. Now with declarative configs, an intelligent delivery system, the system will detect that staging is already on the desired version and skip that push, allowing you to deliver value to your customers faster.

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.

8jy89hui 3 years ago

Complicated static configs are out; Scripts as configs (like Neovim) are in.

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.

  • lamontcg 3 years ago

    If they pull in dependencies that can make them very non-portable, while that is typically where all their power comes from over normal config files.

pmarreck 3 years ago

As a recent NixOS convert, I concur

mbrodersen 3 years ago

Declarative: Specify the result you want. Imperative: Specify (step by step) how to get the result you want.

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.

bastardoperator 3 years ago

You might end up with the nightmare config they highlighted if the person writing the config has no semblance of DRY, but it's unlikely someone versed in CD systems isn't going to use techniques afforded to them for reducing boilerplate.

  • tremon 3 years ago

    The irony is that many declarative systems have very poor support for symbols, templates, and code reusability in general, so declarative configurations carry more boilerplate than imperative configurations.

ayfOP 3 years ago

A good overview of why legacy CD systems cause so much pain in cloud-native environments

alajmo 3 years ago

> The config has now more than doubled in line count, making it impossible to remember the original intent of the configuration.

Not really, I found it quite self-explanatory.

otikik 3 years ago

Hey author: your css seems off on mobile. Text reads fine, but code looks tiny in comparison

sly010 3 years ago

This just looks like a frontend over kubernetes.

  • naphatkrit 3 years ago

    Not exactly? Kubernetes natively doesn’t have a solution for pushing across environments afaik.

est 3 years ago

Ahh yes, the old stuff is new again. Ansible.

moralestapia 3 years ago

>My dad switched from working with a map printout without knowing real-time conditions, the imperative flow, to Google Maps with turn-by-turn directions, the declarative flow.

LOL, he got it backwards, "turn-by-turn directions" is the imperative flow. Can't expect much after reading that.

  • phailhaus 3 years ago

    I was thinking the same thing, but his point is that with Google Maps you simply declare your end goal. It figures out how you should get there based on current conditions.

    • naphatkrit 3 years ago

      I guess I should have said "voice-guided" turn-by-turn huh? https://www.forbes.com/sites/anthonykosner/2012/12/13/google... is what i was referring to :)

      • moralestapia 3 years ago

        Huh? Voice or not, it's still imperative. :grimacing:

        • phailhaus 3 years ago

          "Declarative" and "imperative" are relative to the goal you are trying to achieve. If your goal is only to reach your destination within some parameters, then writing down every single step to get there is imperative. The declarative approach is to specify the destination and those parameters, and allow the engine to determine how to get there.

          • saltcured 3 years ago

            Right, imperative directions would be the turn by turn recipe to get from A to B. Declarative directions would be "be at B". Route planning is the solution method to convert from declarative to imperative navigation directions.

            Of course, it's also a nuanced value judgement. The declarative/imperative abstraction is almost fractal, as we could consider a trip with multiple intermediate destinations as either a declarative itinerary or an imperative plan. Or we could go further and talk about all the control steps it takes to "turn right in 100 meters".

            What the article describes for the father's driving is the difference between ahead-of-time and just-in-time navigation. It's not a very good metaphor for declarative versus imperative configuration management. To turn it into one would make for a perverse story, i.e. telling the father where you want to meet versus telling the father a long list of turns starting from the airport while obfuscating the destination.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection