The true meaning of ‘declarative’
Last week’s advice that SQL (!) is a good language for working programmers to learn early in their careers spawned plenty of comment and follow-up. One aspect that deserves more explanation is my casual characterization of SQL as “declarative”.
That’s uncontroversial; it’s quite common for instructors to mention that SQL (or sometimes only its
SELEC T sublanguage) is declarative. What that really means is not so well understood.
Most commentators last week explained “declarative” as a diagnostic ascription, rather than a mathematical definition. They often express themselves in “negatives”, in that they describe how a particular declarative language achieves its effect without an imperative or procedural specification of algorithm, or without side-effects. A functional programming language like Haskell, for instance, can express the Fibonacci sequence in an intensional definition without specification of an exact sequence of dataflow between memory and CPU. A declarative language gives “why” or “what”, while an imperative language like assembler or C focuses most on the “how” of moving bits around.
That’s the usual model, at least. Application to SQL typically follows these lines: “think about
SELE CT * FROM employees WHERE salary > 50000—’see how the programmer tells the database what records she wants, without worrying exactly in what order they’ll appear or how the database goes about finding them?” While evocative, that kind of illustration leads bright programmers to wonder whether C is declarative, because good C programming generally uses powerful libraries whose implementation details are unspecified: “if I call
qsort(), is that declarative, as I only request the data be sorted and haven’t specified which sort algorithm to do?” A couple rounds more of conversation, and correct use of ‘declarative’ turns out to be foggier than was first apparent.
Here’s the simplest way to think about the qualifier: there are two ‘declarative’-s in play. One is a stylistic, almost aesthetic, adjective, that working programmers use to capture expressive intent. We say that
make is declarative, because its top-level grammar is a domain-specific focus on dependences between files. That’s declarative, in contrast to the procedural command-line
cc -c myProg.c cc -o myProg myProg.o
alternative in a conventional Unix environment.
However, any practical
make source includes procedural elements underneath its declarative surface. Joe Celko, whom I recommended last week for his SQL writing more generally, touches on exactly this subject in his “Procedural, Semi-Procedural and Declarative Programming in SQL“: real-world SQL almost inevitably includes both declarative and procedural aspects. They can even vary in proportion for the same problem coded by different programmers, or by the same programmer in different languages or at different times.
Part of my point last week, which Celko’s article reinforces, is that SQL is good practice in declarative style for those new to it. It certainly is a change.
There’s a second correct use of ‘declarative’. In formal terms, declarative language involves only immutable bindings, or, equivalently, there is referential transparency of sub-expressions throughout the expression. As important as these concepts are in computing theory, most conversations in real-world programming shops seem to me to rest on the first use of ‘declarative’ as an expressive style.
Connection to RUM
What does all of this have to do with Real User Monitoring (RUM)? There are several connections: first, the RUM audience seems apt to me for the message that SQL is good for both career and intellect for typical entry-level programmers. I hope SQL both surprises and rewards them.
Anything about SQL is pertinent for RUM readers, because SQL is so often a bottleneck in real-world end-user experience (EUE) incidents, as I’ve mentioned before.
Finally, there’s a lesson that applies to both SQL and RUM on their own. The best programmers are those with the flexibility to unify and consolidate declarative and procedural styles. High-level domain-specific declarations generally are meaningful to other stakeholders; as programmers, we artfully combine those “business rules” with the lower-level procedural parts to make them real.
Similarly, the great challenge of RUM is to bring together EUE, which is meaningful throughout the organization, with the implementation details of subnet collision incidence, storage latency, and so on, where performance management traditionally focused. RUM done right bridges the two levels.