Recently, I’ve seen a number of discussions along the lines of what makes a language declarative or imperative? (see, in chronological order, William Cook, Bob Harper, and Neel Krishnaswami). Trying to get a crisp definition of vague terms often involves endlessly going around circles. Just in case that isn’t bad enough, in this specific case, some believe that their favourite term connotes superiority, thus setting otherwise reasonable people against each other. Intuitively, the two terms are talking about the level of abstraction of a programming language: declarative means that something provides more what and less how; while imperative is more how and only implicitly what. We have a vague feeling that the terms should be precise opposites, but in practice we keep stumbling across examples which are hard to fit precisely into exactly one of the terms. William and Bob are, I believe, both correct in saying that, if the terms declarative or imperative once had a specific meaning, they have long been robbed of it.
I would suggest that what’s really going on is that we’re trying to use the terms to express absolutes when they can only meaningfully express the relative relation of one thing to another. Before tackling the original point directly, let me try and give an example that is less likely to offend those of us immersed in programming languages. Imagine there are two people competing against each other in a 4km cycle race. Jim takes 5 minutes and Tim takes 4 minutes 50. We might say Tim is quick but we would be incorrect. Tim is only quick relative to Jim. When Kim — who can do the race in 4 minutes 30 — comes into the room, we can clearly see that Tim is not quick compared to Kim. Thus, while relative comparisons between individuals are safe, absolute statements are not: we can’t be sure if we will encounter a person who changes our notions of quickest or slowest. Put more carefully, the absolute statements are only correct for the set of individuals we’ve sampled. Relative statements (e.g. Tim is quicker than Jim) remain correct even when we encounter new individuals.
With that in mind, let’s look at a few examples from programming languages:
- Absolute: Haskell is a declarative language. Incorrect.
- Relative: Haskell is a more declarative language than C. Correct.
- Absolute: C is an imperative language. Incorrect.
- Relative: C is a more imperative language than Haskell. Correct.
Depending on your background, you may find the two incorrects hard to swallow initially. Consider this little C fragment:
a = 1; b = 2; c = 3; d = a + b + c; print_int(d);
and an example compilation of it into ARM assembler [1]:
MOV R0, #1 MOV R1, #2 MOV R2, #3 ADD R3, R0, R1 ADD R3, R3, R2 STMDB R13!, {R0} MOV R0, R3 BL print_int LDMIA R13!, {R0}
Both fragments behave the same, in the sense that they describe the same
output (the printing of the number 6). The C version however states much less
detail about how that output should occur. The assembler version first has to
break the addition down into 2 separate instructions. It then has to save the
original value of R0
to prevent it being clobbered by the
argument being passed to print
. Relative to C, the assembler
version feels much less declarative: one might even be moved to say
that the assembler version is more imperative.
The fun begins when one realises that the assembly version is not the end of the road. We could, if we wished, go all the way down to electrons when considering a program executing. In the opposite direction, a DSL for expressing Context Free Grammars may feel much more declarative than the Haskell program that is generated from it. As that might suggest, we also need to bear in mind that there isn’t a strict hierarchy here. There are multiple dimensions in which different languages can be considered declarative or imperative relative to each other.
This has an interesting implication. For any given two abstraction levels, we can find another in between, or one sideways (and so on). If we wanted to name each, we’d go mad. Thus, proposals such as Neel Krishnaswami’s are interesting thought exercises, but we simply can’t scale them up (unless you find the prospect of a massive, ever-expanding dictionary appealing).
In summary, trying to define declarative and imperative precisely as absolute terms will never work, if it ever did. Using them as relative terms can feel pedantic, but at least has the virtue of not being inaccurate. Although we really only need one of imperative or declarative, I expect both terms to comfortably outlive me, and for people to use them as vague absolutes in the same way that we use quick and slow as vague absolutes. It’s also worth noting that the programming language community is not the only one bedevilled by this issue. The UML folk tie themselves in knots over Platform Independent and Platform Specific Models (PIMs and PSMs). Students of war have spent hundreds of years trying to nail down perfect definitions of strategy and tactics, without success. Humans like the seeming certainty of absolute statements, even in cases where only relative comparisons are safe.
Acknowledgements: My thanks to Carl Friedrich Bolz and Edd Barrett for insightful comments on early drafts of this article. All opinions, errors, omissions, or infelicities, are my own.
Footnotes
It’s been 15 years since I last wrote ARM assembler, so my apologies if I’ve got any minor details wrong.
It’s been 15 years since I last wrote ARM assembler, so my apologies if I’ve got any minor details wrong.