1. 15
  1. 7

    This is just ridiculous. The author works as a marketer at Pulumi. People call Pulumi “imperative” as an insult, so the author is redefining “imperative” to something completely different so that he can claim Pulumi is “declarative”.

    Personally I like Pulumi because it is imperative. If Pulumi was declarative I would not like it.

    Perhaps the author should focus on explaining how imperative is better (in Pulumi) than declarative, rather than creating his own bizarre definitions for “declarative” and “imperative” and gaslighting people into accepting them.

    1. 4

      Pulumi is no more imperative than Terraform’s CDK. And in terraform’s case it’s quite widely understood that the underlying system works in a declarative way. Making it easier to express the declaration by constructing it in an imperative language does not change that.

    2. 6

      Not exactly a hill I care to argue on (especially since I don’t know much about Pulumi), but it feels like the original sin here is trying to call a tool declarative?

      Accepting this premise seems to really push all of this through the looking-glass. Using a config lang doesn’t inherently make something imperative. Using the language to describe what you want and not how to construct it does.

      Yeah?

      1. 5

        Using a config lang doesn’t inherently make something imperative. Using the language to describe what you want and not how to construct it does.

        I think you meant declarative instead of imperative?

        1. 3

          Oof. Hehe. Yes.

          Brains…

      2. 6

        At their core, imperative and declarative differ in one major and fundamental way. Imperative has control flow and declarative does not.

        Yes and no. Declarative systems can have control flow, but it looks like data flow.

        In practice, this is very easy to spot. If your program has statements in it which define if something will happen, checks for errors, and verifies if something was successful, it has control flow.

        This is both true and misleading. Whether it has error checking is a red herring. Most complex systems need error checking. The examples under this show an imperative form that handles errors and a declarative form that does not. A declarative form that handles errors would describe a relationship between states such that an execution agent could construct the correct state depending on whether it was able to create one of the objects that the declarative structure describes.

            type: isMinikube === "true" ? "ClusterIP" : "LoadBalancer", // imperative code!
        

        Wait, this didn’t meet your definition, it does not have control flow, it has data-dependent data flow. You could write this as control flow with an if statement, but this is a conditional expression.

        Importantly, it’s a pure function and so there’s no requirement for any execution order other than that which is implied by data flow. Both of the output strings are constants and so both can be evaluated. In an imperative language, there is a defined execution order but here there doesn’t need to be. There can be in a declarative language too, if the strategy is an explicit part of the abstract machine. For example, Prolog mandates SLD derivation, which leads to a defined execution order, whereas more modern theorem provers tend to decouple the strategy from the logic.

        If the results of the ternary expression can contain side effects such that their execution is observable and directed by the programmer then this is imperative. I don’t know enough about the language the author is describing to know whether this is the case, but in this example the syntax could express either an imperative or a declarative language.

        There are no if statements here. We aren’t checking for success or failure, we’re not using try/catch or any other mechanism. Pulumi is doing it declaratively.

        Uh, what? You have a list of statements that are executed in a defined order, have visible side effects, and whose execution order is controlled explicitly by the programmer. That is pretty close to the definition of imperative: you are telling the computer what to do, you are not describing the state of the system and asking the computer to produce this state via some (possibly well-defined, possibly flexible) mechanism.

        If you’re still really really stuck on this idea, allow me to introduce you to Pulumi YAML

        There then follows a data-flow language that contains a high-level declarative structure with imperative elements. Near the top, it literally has a definition of a subroutine (which it calls a function) that has side effects and is explicitly invoked. That fact that you have to write an AST in YAML rather than something designed for humans does not make it any more declarative than dumping the AST of a C program and calling that declarative.

        The problem, I think, is that there are almost no examples of pure declarative or pure imperative programming languages. HTML (without JavaScript) is a declarative language: you don’t tell the browser how to render or even what order it must render elements in, you just tell it the shape of the final document and let it render however it wishes. HTML isn’t a programming language though.

        Smalltalk is a purely imperative language: when you want to create a Smalltalk class, you send a message to an existing class telling it to create and return a subclass. In contrast, Java, Objective-C, and C++ all have declarative constructs for describing classes and methods that contain imperative code.

        Even Prolog, which is mostly declarative, has cuts, which give you control over execution order and allow you to write imperative-style code.

        1. 2

          I think it’s important to note that with declarative tools, you as an engineer are not creating steps via which your infrastructure will reach your desired state. You are creating the state that you wish to reach. So while in let’s say Ansible, you would create an action that creates 10 EC2 instances, with Pulumi you’d create a state with 10 EC2 instances. While you could make the Ansible action idempotent, so that both ways are functionally equivalent, there is a difference in how you think when creating them. And it doesn’t matter that you created the desired state in Pulumi via an otherwise imperative language, you are still mainly thinking about the state that you want, not how to create it.

          1. 2

            You have a list of statements that are executed in a defined order, have visible side effects, and whose execution order is controlled explicitly by the programmer.

            Yeah, but they don’t have side effects, though. Not in the way imperative usually implies. Instantiating the service object, for instance, doesn’t actually do shit to the infrastructure, it only creates an object holding the definition of the service. The changes are applied not when this code runs, but when some engine gets this graph of definitions and applies. It’s literally and absolutely the same shit that terraform does. Just look at the debug logs of a terraform apply.

          2. 3

            Throughout the whole article I was thinking “If you’re defining how things are supposed to be and then some other code comes and apply your definitions, that’s declarative, surely noone is actually arguing it isn’t because you’re defining it in typescript instead of HCl or y’all”.

            Boy, was I wrong.

            1. 2

              Based on the definition from the other thread, systems like Pulumi and Terraform are declarative because they must perform a basic search (a topological sort) to find a sequence of operations which will construct resources in dependency order. There might exist such a sequence.