I’ve seen many definitions for `declarative programming`. The term `declarative` is often contested because it has connotations and market buzz surrounding it – i.e. there is some advantage to saying your product is `declarative` even if that means stretching the term. I freely admit that I might be guilty of this, too.
My understanding of `declarative` is that it describes a synergistic intersection of several useful properties. Consequently, I can place languages on a lattice for just how declarative they happen to be. This article explains how I reach the definition I favor.
I start with the informal notion of `declaration`:
declaration: an explicit (written or oral) statement or indication of fact, opinion, or belief.
I believe a declarative programming language should enable expressing a program in terms of declarations – not just one big declaration, but a bunch of smaller statements each indicating a fact, opinion, or belief about the program’s behavior. For programming, of course, I’m primarily interested in written declaration.
How am I to recognize that an expression is a declaration? Well, I compare to some ideal properties of declarations.
- Explicitly stating the same fact, opinion, or belief twice in a given context doesn’t affect its meaning or truth.
- Source, location, and origin of a fact, opinion, or belief doesn’t affect its meaning or truth. Though, it is sometimes useful to track origin as part of the fact (e.g. `David believes that declarative is …`).
- Facts, opinions, and beliefs have a `continuous` nature – they hold over time. We can express statements about past events (e.g. `the ball went through the hoop`) but even such statements are true `over time`.
- Facts, opinions, and beliefs will name, describe, and relate objects in an implicit environment or system (e.g. `the ball`, `the hoop`). Thus, the meaning of a declaration is actually a function of its environment.
Formalizing these concepts gives me the properties I now associate with `declarative` expression:
- idempotent: declarative expressions can be duplicated, and duplicate expressions can be eliminated, without affecting behavior of the program.
- commutative: declarative expressions can be reordered or reorganized without changing their meaning.
- concurrent: declarative expressions naturally hold at overlapping times. Declarative systems are implicitly concurrent in the sense of separate expressions holding simultaneously.
- reactive: declarative expressions hold over time and have meaning relative to their implicit environment. If the environment changes over time, so will the active meaning of the declarative expression. [upd. 1/13]
Some people say that declarative is about eliminating `control flow`, but I understand that as a natural consequence of commutativity and concurrency – and to only be skin deep: control flow isn’t implicit in the syntax. In my experience, declarative programming is excellent for modeling ad-hoc control flows and work flows in terms of observations and declared rules of action. I point to Berkeley’s Dedalus paper as offering several good examples.
Some people argue that declarative is about `referential transparency` or `purity`, but I reject this. Declarations don’t have a `value` so much as they have a meaning relative to their implicit environment. However, idempotence and commutativity together do allow most of the same abstraction and refactoring advantages achieved by referentially transparent code.
Some people claim that declarative is about `what not how`, but there isn’t any fundamental reason we cannot declare hows instead of whats. To be honest, I’ve never understood the `what not how` argument – what vs. how is a difference in perspective, whether you’re looking up or down the ladder of abstraction. A clever man might try `why not how`. But, again, this seems orthogonal to the notion of declaration – the decision to declare motivation, intention, or mechanism is just stages of abstraction.
There is simplicity and elegance in having `declarative all the way down` or `everything is declarative`, including IO. The input to such a declarative program is a set of declarations – essentially, another declarative program. And so is the output. That implies a declarative representation of implicit environment and computed demands, of observation and influence. Such a design would be useful for abstraction, staging, typing, metaprogramming, embedding and extending languages. Declarations of what, why, and how are mixed in a single program, together with declarations to combine and compile them.
Of course, the benefits of `everything is a` designs are hardly unique to declarative. Similar benefits have been argued for functional, OOP, procedural.
While I don’t list `everything is a` as a criterion for declarative, it would be a fair point in a comparison between languages if arguing which is `more declarative`. Though, I’m not sure the argument itself would have a point. While `more declarative` is better if all else is equal, all else is probably not equal.
There are many programming models that I count as highly declarative under my definition: constraint, logic, temporal constraint, temporal logic, generative grammars, relational, various forms of dataflow, synchronous reactive, event calculus, functional reactive, and reactive demand. These models vary considerably in other characteristics – composition, security, resource management, completeness, staging, etc.. But they are all very declarative.
On the other hand, there are many systems that are ostensively `declarative` – such as HTML or pure functional programming – that I consider to be relatively weak examples of declarative programming (e.g. due to having poor idempotence or commutativity of expressions). Sure, HTML is more declarative than PostScript. And pure functional is somewhat more declarative than procedural (and certainly more cleanly staged). But they aren’t very high on the declarative lattice.
I posit that functional programming is `declarative` primarily to the extent one is functionally constructing a declarative program.