Hacker News new | past | comments | ask | show | jobs | submit login
Why does nobody seem to know what imperative and declarative mean? (leebriggs.co.uk)
57 points by zdw on July 29, 2022 | hide | past | favorite | 72 comments



Learn the difference between imperative and declarative!

Knowing the difference between imperative and declarative is very important.


Let us discuss this more. If I were more knowledgeable of this, there would surely be benefits.


I see what you did there.


Look at what you did there.


Yes, Pulumi is nouns not verbs. It is declarative. Like Terraform, you're not saying: "make me an virtual machine," you're saying, "this virtual machine", which gets created if needed.

But like Terraform can be if you start importing data-sources, Pulumi can get dynamic. Your Pulumi code might have some actions/verbs it does to figure out what to write. You might slip up one day and get a little bit pregnant. Oops I mean get a little bit imperative. In Terraform, these external dependencies are declared, are declarative style, but they carry the risk of imperativeness, in that they don't have idempotency, they don't clearly delineate what is expected: they're commands-as-structures, actions-as-structures, executed via higher level systems.

Pulumi might not be imperative, but there's less clear lines (unlike an explicit Terraform data-source) & it's less clear when you get a little bit imperative.


I gotta say, the core argument in this article is quite weak.

Here it is, quoted from the middle of the article:

    While the operation of checking the value of isMinikube is indeed imperative, the result is still declarative. What does this mean?

    Pulumi as a tool is declarative.

    It’s the language you’re using to write things that’s imperative, not Pulumi itself.
Ooo.. fighting words.

So let me get this straight, if a tool that uses an imperative language emits a declarative configuration that the tool executes, the original tool is declarative and not imperative.

…because, and this is the core argument here: at the execution step, the commands that are executed, are executed in sequence by the tool without flow control, except that which is internal to the tool itself (eg. check if s3 bucket is present, if so, don’t create it).

So, for example, if I compile source code into a assembly, and then run it… the assembly is executed step by step.

…so, my code is declarative then is it? Since the artifacts emitted by the compile step are a linear sequence of steps with no flow control other than what the compiler emits?

This is just pedantic nonsense.

Look, flat out: if you apply flow control, it is imperative.

It’s completely ridiculous to say that if you generate a declarative configuration in an imperative manner, and execute the declarative configuration, the end-to-end process is declarative.

It’s not. It’s imperative.

No amount of redefining terms is going to change that.

If you want, you could invent something called “declarative infrastructure deployment”, which is where the end user does not manually define imperative code to check the infrastructure state, and relies on the tool to check and resolve the “desired state”. You could also call it “desired state deployments”, or “cool deployments”.

The you could say, sure these tools either are or are not conforming to your definition of this type of tool.

Sure, fine. That’s easy to verify; does the tool expose primitives to express flow control in response to deployed infrastructure state explicitly? It’s a yes / no.

…but, that doesn’t make those tools declarative. They’re just “whatever term you invented here” tools. …but putting “declarative” in the name you invented doesn’t make it declarative.

Pulumi has flow control. It is imperative. QED.


The tool takes in a data structure that doesn't encode any control flow. How that data structure is generated is a separate issue. I'm sure there are tools to imperatively generate whatever input Terraform needs (yaml?), does that make Terraform declarative as well?

Fundamentally the model is still: this is the state I want, and Pulumi figures out how to make that happen. Just like Terraform. The specification of that state can be static, fill-in-the-blank templated, or make use of imperative logic to build the final data structure. Just like Terraform, albeit one step removed because of the yaml intermediary.

Is it easier to write an imperatively-specified boondoggle of a state because of the direct access to an imperative runtime, sure. But that's against best practice and not what they're advocating here. Hell, I could hide the ternary behind a declarative interface (function) and then what's the difference between the imperative code hidden by my function and the imperative code hidden behind Pulumi's or Terraform's state evaluation and application functions?


I’m not asserting that terraform is declarative, or that pulumi and terraform are technically different.

I’m saying, this article is about the pedantic definition of what it means to be declarative vs imperative.

…and pedantically, if you’re partially imperative, you’re imperative.

Otherwise, you’re choosing to redefine the terms to suit your convenience.

Which you’re welcome to do too… but you can’t pedantically tell people they don’t know what a term means and redefine it to mean something convenient.


The author is making a pedantic point. While I agree with the main thesis that Pulumi *as a tool* is declarative, one of the main selling features (and the first thing I think of with Pulumi) is how languages like Python can be used to write it. Hence, the association of everyone besides the the author with Pulumi == imperative code.

The imperative vs declarative argument has been used to keep Pulumi out of several infra repos I have been responsible for. A ball of horrible Terraform is much better than a ball of terrible Node.js Pulumi.

https://twitter.com/dave_universetf/status/14864638523131330...


Is Vagrant suddenly not declarative just because the config is written in Ruby?

Also that rant is just a woeful misunderstanding of well everything, but in particular AWS and k8s. If you do it in Terraform you couldn’t specify the AZs either and instead have to define a VPC config with two subnets. That’s an AWSism not Pulimi.

“I will learn nothing and complain the whole time!” isn’t as damning of a criticism as they think.


Not to a Node.js programmer!


Randy's law of config files:

In the limit, all config files become a language.

So if you have a so-called "declarative" config file, it eventually becomes a ... ? Language - that's imperative. Every time. Yes, really.

What is the only time this doesn't happen? If the config system ceases to exist.


It doesn't have to become an imperative language. It could turn into Prolog.


I think it's just a weakness of the terms. Kind of like "interpreted vs compiled" at this point very little is strictly interpreted, almost everything goes through a bytecode compilation step.

In this case, you're using imperative languages to generate declarative policies. Things just aren't black and white.

Importantly, it doesn't really matter. We say "Python is interpreted" because the it gives you the UX of an interpreted language. We say "Pulumi is imperative" because it gives you the UX of an imperative language. Under the hood they do their own thing, but unless you're trying to have a really precise conversation it's usually easiest to stick to those terms.


> We say "Pulumi is imperative" because it gives you the UX of an imperative language. Under the hood they do their own thing, but unless you're trying to have a really precise conversation it's usually easiest to stick to those terms.

I'd still take issue with that description. There is a massive UX distinction here that isn't under the hood.

Your imperative Pulumi code is at a meta/macro/preprocessor level. That's completely different from writing imperative code that manipulates your servers.

Your code generates declarations. It does not take actions.

Boiling that down to one word is likely not the best way of doing it, but I agree with the author that in this situation the function/output of what you write is more important than how it gets there. The stuff you write doesn't configure servers, it describes servers.


I think ultimately you're just demonstrating the larger point. There is no value trying to label it as imperative or declarative except in the niche situation where one is debating whether it is imperative or declarative, which at some point is just a philosophical question.

I think it was Plato who asked this question - if I take two pieces of clay and smash them together, which piece grew? You can argue all day about it, and it has some fun implications about identity, but that's philosophy.


If you care about the type of thing you need to write, there's tons of reason to make the distinction.

The thing about writing "code that outputs a data structure" is that you can voluntarily choose to use no control flow.

The tool takes declarative input, and optionally you can use imperative code to make the job of writing that input easier.

That's very different from a tool that gives you helper functions for writing an imperative core. In that case you're always writing something that's full of control flow, and it's up to you to make sure you get all the state transitions to be consistent with each other.


In my experience, the critique of Pulumi and the CDK came down to not using the Cloud Formation service directly and adding needless abstractions over it.

I didn't see much complaint about imperative vs declarative.


Isn't that what Terraform does, too?


I think so, yes.


"It is ever the province of specialists to overstate their domain. Generalists don't know too much or a lot of anything in particular!"


>Unless I’m mistaken, Terraform was the first tool to drive home the importance of defining infrastructure declaratively.

Puppet started popularizing declarative configuration management almost a full decade earlier.


Puppet is declarative configuration. Terraform is declarative infrastructure. Usually you'd use both together (or replacing one with something similar, like Ansible instead of Puppet).

Idea is, you run Terraform to create/modify the infrastructure, and Puppet to configure the software running on that infrastructure.


Declarative: Describe the result you want (without any implementation details).

Imperative: Describe step by step how a global mutable state is manipulated.

Functional: Describe how an immutable value is calculated.


Would it help if functional langs were instead called declarative?


Well, if you use the definition of "functional" that is the most popular nowadays, you will just lose some specificity, because there are many other declarative language paradigms besides the functional.


Perhaps have a hierarchy where functional is a subset of declarative but introduce it to the public as declarative first then delve into functional as needed


Well, "introduce it to the public" is quite a badly defined action, isn't it?

I mean, what practical thing do you think people should do differently?

Besides, we are just arguing about the definition of some words that people can't really agree on their meaning (most people are close to each other, so the words aren't meaningless, but they are still different). Piling-up more of those words isn't a benefit.


Taking a moment to plug https://dhall-lang.org/, a truly non-imperative solution in this space


As much as I like Dhall, it needs unions like in typescript so we don't see `Some` littered everywhere.


Like other forms of subtyping, unions make type inference much harder - typically undecidable. This is, of course, incompatible with guaranteed termination.


edit: fixed a mistake where I put imperative instead of declarative

So what I get from this is that Pulumi is declarative, but authoring Pulumi is imperative (unless you’re using an declarative language, which afaik Pulumi doesn’t support any).

I’m a pretty pedantic person, so I’d probably make this same argument once I had the knowledge. I don’t see what it changes for a developer though. If I’m writing imperative code then the tool running my IaC might as well be imperative. I guess it’s a cool piece of trivia to know though.


By that logic everything is imperative. Authoring the data structure for whatever IaC tool is going to take on the characteristics of however you author it. Is the same yaml detailing the desired state declarative when I hand-write it and imperative when I use some language to generate it?


I would say yes. If you're writing that YML by hand, and that YML doesn't have extensions that provides control-flow (e.g. CloudFormation's if syntax), then you're writing something declarative. If you're using an imperative language, and you're using control-flow, then you're using that tool in an imperative way.

It doesn't make sense to define the attributes of a tool by its artifacts. If we applied that logic across the board then Haskell isn't a purely functional language because it eventually gets compiled to machine code which is imperative.

What matters is the source, not the artifact. In the case of Pulumi you are using imperative languages, so Pulumi is effectively imperative, although it can technically be considered declarative when you consider the Pulumi runtime. If Pulumi had support for a declarative language, then I would consider it a declarative tool.


I wrote my own Ruby code to manage Kubernetes manifests. Instead of messing with templates, I generate or transform manifests, sometimes functionally, sometimes not.

Declarative is necessary if one is trying to have something maintain a desired state and do what is necessary to move the current state to the desired state.

I wonder if this is why I hear a lot of people say K8S is complex. I mean, it isn’t simple, but it isn’t _that_ complex.


> unless you’re using an declarative language, which afaik Pulumi doesn’t support any

Pulumi has a YAML evaluator, with many of the same limitations of Terraform being imposed.


Yes, sadly, I have seen this too many times.


TLDR: everyone does know what imperative and declarative mean, but when it comes to Pulumi (an infrastructure-as-code development kit), nobody other than the author makes a distinction between Pulumi itself (declarative) and the code that uses Pulumi (imperative).


Is the YAML language runtime imperative?

It’s an important distinction to make because passionate CDK/Terraform/CloudFormation defenders use the dichotomy to dismiss different tools.


YAML is a serialization language so it depends on what you're serializing and how it's used


Great comments so far ;)


Amazing rant but I really wish they gave you a quick tldr on what the fuck the difference is between imperitive and declarative.


It's much like the difference between "how" and "what". SQL select statements are my favored example of declarative: you simply "declare" what you want ("all rows from this table with these values in those columns") and it's up to an engine to actually figure out how to do that for you. In an imperative language like C you could produce the same output, but you'd be specifying how, not just what: a for loop to iterate over each row in the table, if statements to compare values, etc.


But each layer’s what is the layer above its how. My C code is just declaring the program I want to run. How it is run is determined by a compiler, assembler, linker, operating system, and CPU.

There’s a sort of useless sense in which everything is declarative if you are allowed to shift around what the relevant “thing to be done” is, and it seems like many people are complaining that the article does this exact thing.


Sure it's a spectrum and the exact edges can be blurry. But no-one's going to argue that there isn't a qualitative difference between a sql query and the same logic written out in c


And I’m not arguing that. The article is dealing with those blurry edges, though, and what layer “matters” in evaluating the distinction is very much at issue. So if we’re going to compare to SQL/C, we need to bring in the relevant analogous nuances


Flow control? Imperative.

No flow control? Declarative.

What is flow control? Typically if/then/else statements.

Importantly, setting a variable with a conditional does not make the language necessarily imperative.

I wonder if a language can be Turing-complete without flow control. My guess is no?


I'm not even sure I agree with the author with that specifically:

> Flow control? Imperative. > No flow control? Declarative.

I think I'd rather say:

Imperative: Tell the system what to do.

Declarative: Tell the system what you would like to be. The system figures out what to do to get there.

You could well have flow control to say things like "if things are like that, I want things to be such", or "here is a loop building up what I would the state of the system to look like", still without telling the system what to do to get to that state.

> I wonder if a language can be Turing-complete without flow control. My guess is no?

Depends on your definition of "flow control". Untyped lambda calculous is effectively entirely just declaration and application of anonymous functions with only one argument, but it's Turing complete.

You can even try it in any language that has lambdas as first class citizens, e.g. here's a factorial function I wrote in Lambda calculous, but implemented in python:

    fac = ((lambda p: p(p)(lambda q: lambda n: ((n(lambda e: lambda e:
      lambda a: a)(lambda i: lambda l: i))(lambda d: lambda c: c)
      (lambda m: (lambda m: lambda z: m(n(z)))(q(m)))((lambda c: lambda x:
      n(lambda g: lambda h: h(g(c)))(lambda u: x)(lambda u: u))))))
      (lambda s: lambda q: lambda w: q(s(s)(q))(w)))
Does it have flow control? Depends. No python flow control is used, but some of those lambdas implement flow control. But they're not any part of the language (Lambda calculous), we just build them.

Would I call Lambda calculous declarative? Not at all!


> Depends on your definition of "flow control".

Yep. That’s why this is a potentially useful rule of thumb for comparing programming languages, especially ones that skew very far in one direction or the other, but it’s not a rigorous definition of the concepts.

Some incredibly simple algorithms are difficult to explain naturally without it sounding like control flow. How would you describe the absolute value function without saying “if” or “when” or specifying one particular element from a set? Obviously any computer system needs to be able to determine what steps to take depending on specified conditions.


> How would you describe the absolute value function without saying “if” or “when” or specifying one particular element from a set?

I don't think you even want a conditional when describing the absolute value function on the complex numbers.


> How would you describe the absolute value function without saying “if” or “when

How is this? absolute value is distance a number is from 0.

I get your point, though.


True, the syntax will obviously depend on what primitives the language provides. Of course it could also just provide the abs() function directly.


The author seems as confused as the people he complains about.

Just his example of `x = a ? b : c`, I never saw anybody try to claim code like this makes a language imperative. Instead, I've seen many people calling this logic "functional if".

If you go with his definition, only simple variable declaration is declarative.

(By the way, a very popular definition is that imperative languages have flow, not flow control. AKA, it makes a difference if you go and execute the instructions on a different order. That one is reasonable, but then, why single out non-associativity of operations when there are many other things that no language makes non-associative?)


Prolog would like to have a word.


> I wonder if a language can be Turing-complete without flow control. My guess is no?

I agree, unless something like pattern-matching does not count as control flow. But then isn't a conditional just a basic pattern? Why does it get a pass?


> I wonder if a language can be Turing-complete without flow control. My guess is no?

I can’t imagine why not. I think you have to make the distinction between explicit control flow in the syntax, which declarative languages don’t have (according to this rule of thumb), and the notion of a computer choosing to do one thing among several options depending on some condition. Of course any Turing-complete system can do the latter, but that’s totally unrelated to the syntax of the programming language.


As an example, declarative would be something like

    squares = map(square, inputs)
While imperative would be a for loop, store this here, multiply that with this, push that onto the end of my results array, etc


Function calls don't make something declarative, otherwise C is declarative. And once you accept that, then words have no meaning anymore and we can't meaningfully discuss the topic.


> Function calls don't make something declarative

Expressions instead of statements is a key tool to make things declarative; the functional and logic paradigms are both declarative programming paradigms, as opposed to the structured/procedural and OO paradigms, which are imperative.


I don't know what you're trying to get at, you seem to be agreeing with me. Function calls (present in C and other structured, procedural, and OO paradigm languages which are also imperative) do not make a language declarative.

That they're also present in the functional and logic paradigms doesn't change that. It's a feature common to both, so not a feature that can be used to distinguish the categories from each other.

And regarding expressions, Rust is more expression oriented than most mainstream procedural languages today, and it's not declarative, though it may have more declarative-style (particularly its heavy use of the iterator style) compared to many other imperative languages.


> Function calls (present in C and other structured, procedural, and OO paradigm languages which are also imperative) do not make a language declarative.

“Declarative” and “imperative” are not really features of languages in the first place. They are features of code (and coding paradigms), and you can write code of either style (and usually most paradigms) in almost any real-world, Turing-complete, higher-level-than-assembly language.

The specific replacement of an imperative loop with a map call you made the non-sequitur response about function calls to upthread however, made the code it was in more declarative, though, and is typical of the ways that the functional paradigm (where map is typically the idiomatic way to do that) is more declarative than the structured/procedural paradigm (where building up a collection in an imperative loop would be.)


Exactly. You could even write declarative style C, or make your code _more_ declarative. It’s not some binary decision. It generally just means writing higher level language describing the result you want, rather than all the instructions to produce it. Functional languages are very good at letting you right declarative code, especially when it comes to dealing with sequences and lists. Ruby’s enumerator in particular allows you to write code in that style, rather than complicated nested for loops full of conditional logic.


Unless they're talking about layers, with the function prototypes and function calls being declarative, and the next layer down being where the code is actually written out. Even that doesn't really work, though; main() usually has code in it... <shrug>


They do.

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


That’s a great rule of thumb to quickly identify whether some new syntax is more likely to be imperative or declarative, but it’s not a great explanation of what the two terms mean for someone new to the terms. A banana doesn’t have control flow but it’s definitely not a declarative programming language.


I would eat a declarative banana.


In fact, one should eat a declarative banana.


The declarative banana should be in my stomach.


After the 20 or so comments that responded I'm still no closer to understanding.

The SQL guy sort of made sense I guess maybe.


The "has control flow" vs "doesn't have control flow" description is a bad one, an attempt to reduce "declarative vs imperative" down to syntax instead of semantics. The parent to my comment is still in imperative style, despite having no control flow: It's specifying what actions to take to reach the desired outcome ("eat"). Declarative is describing the desired outcome, then letting the system figure out how to get there.

When you write an SQL query, you're not describing steps to take, you're describing the outcome you want (what rows and fields from which tables and how those tables relate to each other). The query engine then takes that description and figures out how to actually do the query (what indices to use, in what order, how to actually loop/hash/map/scan the tables and indices, etc). This is what EXPLAIN shows you, the actual imperative plan the query engine came up with given the declarative query you gave it.

Another common example would be CSS: It's all about describing the desired result and spatial relationships between elements, then the CSS engine takes those rules and figures out how to actually get those elements to do what you want. Like something as seemingly simple as "float: left" - for the text to wrap around it, the engine has to deal with the image size, position, font and font size, figuring out when to wrap the text, height of the font, which line of text is greater than the image height, etc. The engine does a lot of work figuring out the steps to take to get the result you've described.


Example Problem: Calculate the sum of the first N natural numbers.

Declarative: N(N+1)/2

Imperative: for(int sum=0, int i=1; i<=N; i++) {sum = sum+i;}


I can’t take him seriously! He didn’t like pineapple on pizza!!!!




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: