16

From my understanding, in declarative programming, programmer only need to define the end result, but not how to compute it. But for execute that said function, the function must be pre-defined by the language, that instruct the machine to compute, step by step, for user to use in the first place - hence it implementation is imperative.

So is declarative programming just imperative programming 'under the hood'? If so then how to differentiate them two?

And if I happen to write a very complex program (or any program at all), at some point I will have to give implicitly step-by-step instruction, is there any way I can get around this and use the declarative approach instead?

8
  • 113
    Everything is just processors executing opcodes "under the hood". The entire point of software engineering is to design better hoods, so that humans can deal with code in a format better suited to our perceptory system than opcode streams. Commented Aug 21, 2023 at 10:38
  • 11
    The question "how to distinguish them?" is a strange question since you already know the answer. A declarative language is differentiated from an imperative language by whether it declares an intention or specifies a command. That's how you distinguish them. Languages are distinguished by their characteristics, not by their implementation details. Commented Aug 21, 2023 at 20:14
  • 17
    I mean, we could turn your question around just as easily. Every imperative language is just a description of the desired evolution of an abstract machine; we "declare" what changes we want that machine to undergo, and the compiler spits out a program that simulates that abstract machine. So, isn't every imperative language really declarative then? This is just playing with semantics. Commented Aug 21, 2023 at 20:17
  • 3
    This question needs a lot more clarity before it can have a concise answer. What are you really asking here? Commented Aug 21, 2023 at 20:18
  • 10
    For an interesting view from the opposite side, HDLs often have imperative features that the compiler converts into a declarative hardware structure.
    – jpa
    Commented Aug 22, 2023 at 5:42

7 Answers 7

21

Differences

So is declarative programming just imperative programming 'under the hood'? If so then how to differentiate them two?

Two of the major differences between declarative and imperative programming are mutation and eager evaluation.

Mutation

If you are writing some code, just ask yourself: "After I define variable x, am I able to reassign it to something else?" If the answer is: "No, not at all", then you are probably working in a declarative language.

Eager Evaluation

If you write an expression, ask yourself: "Can I enumerate exactly when and where this expression will be evaluated, even with respect to other expressions?" If the answer is "No, the language decides that because the order does not change the output", then you are probably using a declarative language.

Limits

And if I happen to write a very complex program (or any program at all), at some point I will have to give implicitly step-by-step instruction, is there any way I can get around this and use the declarative approach instead?

Well, a more precise way to say this is that every computation produces an output that depends upon its inputs (though a write-only program may have the empty set as its input). And at the minimum, the programmer must specify what parts of the output depend on which parts of the input. You can't properly specify a computation without providing this information. What you may or may not need to do, however, is specify the order in which those evaluations take place.

Let's consider a mostly declarative language that hopefully a lot of people are familiar with: SQL. Now, UPDATE and DELETE queries make it imperative, so let's just consider the subset using SELECT and INSERT. You can perform some fairly complex calculations with just these primitives. And you can perform them on very large tables. If they are indexed properly, something somewhat magical happens: when you run the query, you get the result in a reasonable amount of time, even though you didn't specify the manner in which the tables should be joined.

You can somewhat force a join order by using sub-select statements, but this would almost never be considered best practice. In general, it is enough to tell the query engine which tables contain the rows of interest, and let it decide how to join those tables to produce the desired result. At no point are you ever required to direct the query engine to join tables in a particular order, or to use one index or another.

At the same time, you can investigate whether the query engine is doing a good job by running EXPLAIN PLAN. This lifts the hood so you can see the "imperative engine running inside". But if you follow best practices for creating indices and regularly updating statistics, then this is not necessary. It doesn't matter whether you are querying 10 rows from a table with 1000 or querying tens of thousands of rows from a join of dozens of tables. The entire goal of SQL is to remove the need for the programmer to tell the query engine how to do its job.

A similar process is at work in other declarative languages.

29
  • 12
    SQL is a great example because compilation is not just machine/platform dependent, it is also data dependent and the same query running on the same database can become different imperative instructions depending on parameters or how much data there is at runtime.
    – Cong Chen
    Commented Aug 21, 2023 at 20:20
  • 2
    If you're allowed to interleave SELECT and INSERT queries, you can still do "imperative" programming in SQL. (INSERT mutates global state, after all.) But I assume you mean something like "using only SELECT (and INSERT to initially populate the database)". Commented Aug 22, 2023 at 11:00
  • 3
    "The entire goal of SQL is to remove the need for the programmer to tell the query engine how to do its job." - isn't that really true for every language? You use C to avoid telling the combined compiler/machine instruction engine how to do its job, which registers to use, etc.
    – Steve
    Commented Aug 22, 2023 at 12:49
  • 2
    @Barmar, yes but that's exactly how optimising compilers work in ordinary languages. You say a = b + c; d = e + f; in C, and you've no idea what registers are used, whether the statements execute in reverse order, whether a function call is inlined, whatever. And that's before you even talk about pre-emptive multi-tasking at the OS level, branch prediction at the CPU hardware level, the list is endless of things that are fiddled with in the name of optimisation and systemic balancing.
    – Steve
    Commented Aug 22, 2023 at 15:48
  • 2
    @Steve What it compiles to is irrelevant. We're talking about the abstraction level of the source code. When you write sequential statements in C, you think of them as happening in order. But when you write SQL, there's no order (no one thinks t1 JOIN t2 is different from t2 JOIN t1.
    – Barmar
    Commented Aug 22, 2023 at 15:51
28

At a lower level, every(?) CPU in current use is essentially imperative so yes, everything has to be imperative at some level.

However, to some extent that's not really important: everything is an abstraction and the real question is "is this abstraction useful?", and we certainly find that declarative programming is a useful abstraction in many circumstances. Sure, if you're actually implementing a declarative programming language you may have to write some imperative code, but that's the same as if you're implementing an imperative language at some point you have to write some assembly. And if you're implementing a CPU, you have at some point to deal with transistors etc.

4
  • 2
    LawnMowerMan's answer asks "Can I enumerate exactly when and where this expression will be evaluated, even with respect to other expressions"? For every major CPU, the answer is no. Out-of-Order Execution is pretty much a given at Gigahertz speeds. But OOE is hidden at a higher layer. And the immutable results? See register renaming, another optimization. Even if you write twice to EAX, the results can still end up in different physical registers.
    – MSalters
    Commented Aug 22, 2023 at 12:19
  • 3
    @MSalters And indeed from the perspective of a CPU the instruction set is just a declarative language for describing a data flow graph even though it resembles an imperative language (and once was evaluated in exactly that manner). A truly imperative language precludes the possibility of optimization, hence they tend to evolve into declarative languages over time. So you could even say "imperative programming is declarative under the hood"! Commented Aug 22, 2023 at 21:47
  • Who said you have to evaluate declarative programs on CPUs?
    – einpoklum
    Commented Aug 23, 2023 at 8:57
  • @einpoklum Practically that is what people do >99.9% of the time (acknowledging things like VHDL as mentioned in Jiří's answer) Commented Aug 23, 2023 at 10:54
11

To expand already existing answers, there are languages like VHDL which are mainly for writing declarative code and they can produce truly declarative output in the form of hardware, which executes "everything at once", so the order of execution is irrelevant.

On the other hand, there exists high level synthesis which takes imperative language like C and then the final output is hardware, so the imperative programming languages can be "declarative under the hood".

So the answer is that imperative/declarative code can be imperative/declarative under the hood. You have to differentiate the languages by their features. You can tell language is declarative mostly by the fact you can't reassign variables or it doesn't make sense and that it doesn't have loops in normal sense.

4

All programming languages are essentially abstractions for humans to express problems, and automate the solution to those problems. The difference between declarative and imperative is in how they allow and encourage programmers to express their problems, and as a consequence how the solutions to those problems are automated.

The existence of a lower-level implementation written in a different abstraction is irrelevant, except to the extent that practical implementations do not form perfect abstractions, so "leak" details all the way down to electrical interactions.

In an imperative language, the abstraction provided by the language is that of a sequence of actions, specified in chronological order. The language definition will generally guarantee that the observed behaviour of the program is consistent with that order of operations. At a lower level, an optimising compiler might rearrange some of those operations where it can prove it will not change the observed result; and a processor may evaluate branches in advance and discard the result if that path is not taken. Those abstractions may leak, leading to bugs and vulnerabilities, but the primary abstraction of the language is that things happen in order.

In a declarative language, the abstraction provided is that of a set of declarations, which may use source code order to define dependencies and other relationships, but are not laid out in a strict chronological order. The compiler or execution engine has much more freedom to rearrange the order of operations, or choose between multiple implementations of a particular operation, to achieve the requested result. Generally, such languages are modelled as having immutable data structures and side-effect free operations - the program is more closely related to a mathematical proof than a recipe. Again, the abstraction might leak, and the programmer may need to force a particular order of operations, but the primary abstraction of the language is that statements are re-orderable.

As with most categorisations, these are not black and white: a language can incorporate features of both styles, and be "more declarative" or "more imperative". A C program written in a Functional Programming style might allow a compiler to make more assumptions than one written in a traditional Procedural style, but most C programs contain a large amount of code that is imperative - i.e. reasoned about as an ordered sequence of operations.

2

A programming language is only a mean to express some abstract computational solution, independently of how the language abstractions will be implemented.

So no, it's not just imperative under the hood. The language implementation may transform the higher language in imperative code. Or it may apply imperative code to deal with the non imperative stuff (e.g. term rewriting, unification, etc...). But you don't have to care, you just have to select the most suitable language for the kind of problems you're trying to solve.

It's not because every language ends up being executed in machine code, that one could say every programming language is only machine code under the hood.

1
  • I'm a little confused by the last sentence. Did you mean "it's not because" or "it's because"?
    – JimmyJames
    Commented Aug 23, 2023 at 17:22
0

Is declarative programming just imperative programming 'under the hood'? If so then how to differentiate them two?

Yes, declarative programming is imperative programming 'under the hood'. In fact, all declarative programming languages are implemented in another imperative programming language (directly or indirectly).

But to differentiate between them you should not look 'under the hood'. You should look at the controls, from the point of view of the driver. Does it have a steering wheel? If yes, then it's a normal car. Do you enter the destination in a map and then just wait to get there? Then it's a fully autonomous car.

It's the same way with programming languages: do you specify each step that needs to be done? Then it's an imperative language. Do you just specify the characteristics of the output, without specifying how to compute the results? Then it's a declarative language.

-3

I'd argue that the difference between imperative and declarative is simply the presence of an optimising compiler.

I'd also argue that nowadays the distinction isn't a useful one.

The classic example given of a declarative language is SQL, and that's because once upon a time its optimising capabilities and execution engine had no recognisable analogy in languages deemed imperative.

But that kind of talk is old hat. Nowadays, powerful optimising compilers are completely ordinary amongst basically all languages (certainly commercial compilers for mainstream languages), as are various kinds of execution engines which supervise and alter their own workings dynamically to improve efficiency.

35
  • 2
    Optimising compilers can only rearrange those parts of a language which can be proven to be free of side-effects - that is, the parts of the language which are in fact declarative. What we generally mean by an "imperative language" is one where large amounts of code will not be in that category. Even a mostly-declarative language like an SQL dialect may have parts with side effects, which are therefore imperative; this then limits the optimisation capabilities of the compiler, see e.g. PostgreSQL's function volatility categories.
    – IMSoP
    Commented Aug 22, 2023 at 11:20
  • 3
    @IMSoP, few things in the database world are side-effect free. Even a plain query causes writes, and is capable of deadlocking or degrading performance in a way that has non-local effects. A fully-declarative language, if "declarative" means an absence of "side effects", is synonymous with static data. Most programming languages are concerned with specifying computation/algorithms, not describing static data.
    – Steve
    Commented Aug 22, 2023 at 12:40
  • 1
    @IMSoP, there's no compiler that guarantees not to optimise. What you call "guarantees" are often just de-facto behaviour, which will be overhauled tomorrow if some part of the system becomes sophisticated enough to analyse whether an optimisation is possible. For example, most branches are guaranteed not to execute unless the test passes - but then CPUs execute them in advance anyway, and discard the results if the test fails. There's simply nothing sacred anymore.
    – Steve
    Commented Aug 22, 2023 at 18:26
  • 1
    While it is true that an optimizing compiler can detect portions of imperative code which can be handled as-if it were declarative, there are strong limits to the extent this can go. In the other direction, immutability in a pure (declarative) language can make some optimizations like in-place sorting complicated to impossible. A naive programmer would not even know it's an issue. So I think that saying the distinction isn't useful is overstating the case. ChatGPT is definitely that naive programmer that is happy to give elegant and poorly-performing code in many languages. Commented Aug 22, 2023 at 19:43
  • 1
    @IMSoP, I think the problem is that I find SQL can be fully reduced to an imperative interpretation. I view a query as essentially specifying a series of imperative operations. The engine can reorder, but only if the results would be "the same" as what I have already specified imperatively. Another aspect is that I treat source code not just as machine instructions, but as something that often encodes things that aren't important for the machine, but does say something to the reader or corresponds to my thinking. Code usually contains only solutions, not all of "the problem" as I know it.
    – Steve
    Commented Aug 23, 2023 at 13:05

Not the answer you're looking for? Browse other questions tagged or ask your own question.