The Declarative Delusion
Jakob Jenkov |
I often read or hear developers saying that declarative programming languages are better than imperative programming languages. I believe that to be a mistake. In my opinion declarative programming languages are never better than imperative programming languages. Don't get me wrong. There are definitely cases for which a declarative approach is more appropriate than an imperative approach, but it is far from every case, and a declarative approach does not necessarily mean you have to use a declarative language. In this text I will explain why.
Declarative vs Imperative Programming Languages
Before I get into an argument about declarative and imperative programming languages, let me just take a moment to explain what they are.
A declarative programming language is a language which enables you to express what you want done, and not explicitly how you want it done. Examples of declarative languages are HTML, XML, CSS, JSON and SQL, and there are more.
An imperative programming language is a language that express how you want something done, as a series of smaller steps of a whole. Examples of imperative languages are C, C++, C#, Java, JavaScript, Scala, Ruby, Python, and there are way more out there.
Declarative programming languages are typically designed to address a very specific problem.
Imperative languages typically are designed to let you address any kind of problem.
The Declarative Delusion Defined
Declarative programming languages are not crap by default. The delusion happens when developers start believing that declarative languages are better than imperative languages. This belief is what I call the declarative delusion.
The declarative delusion makes developers believe that just specifying what is much better than specifying how. The delusion probably stems from the fact that it is often (but not always) shorter to specify what, than how.
Developers suffering from the declarative delusion will always gravitate towards a declarative language or approach, even in situations where an imperative approach or language would be much better.
A Developer's Job is Imperative
Before I even explain why declarative languages are far from always better than imperative, let me first examine the whole infatuation with what over how.
As a developer, your whole job consist of finding out how to do things. Your customers, managers, founders etc. are the ones who specify what and you need to find a way to get it done. If you are a founding developer, you get to say what to do, but you are still left with the job of finding out how to do it too.
I can understand why just having to specify what feels good. It's short, and the how becomes somebody elses problem. It's like giving orders. Humans often like giving orders (unfortunately).
Luckily, we developers get to give orders too from time to time. When we delegate work to other developers or subcontractors, and when we call APIs, or use declarative languages or approaches. But - developer's job is inherently imperative even if parts can be handled declaratively. Therefore it doesn't make sense as a developer to favor the what over the how. The how is our job. It's what we do.
Declarative Programs are Executed by Imperative Program
A computer cannot execute what. A computer need to know explicitly how to do it, step for step. It can only execute how. Therefore a declarative program is always an input to an imperative program which figures out how to execute the what. Declarative and imperative go hand in hand.
The Declarative Strengths
Declarative languages are often designed to address a very specific problem or use case (domain). In other words, declarative languages are often domain specific languages (DSL).
Being domain specific, targeted at a very specific problem domain, declarative languages are often very concise. The language doesn't need features for every possible problem on the planet. Just enough to solve problems within its domain.
Second, declarative languages may sometimes be easier to learn for non-programmers. Since declarative languages are domain specific, the language often looks closer to the verbal language used within that domain. For instance, SQL has been learned by lots of non-programmers. If you are a data analyst and you understand data, relational databases, and know what questions you want to answer with that data, SQL will feel more natural to you than e.g. JavaScript.
Third, since declarative languages may be easier to learn for non-programmers, declarative DSLs can be a way to empower non-programmers in an organisation. A DSL may enable non-programmers to have a more advanced interaction with a system. For instance, data analysts can create their own reports rather than having to get a developer to do it.
The Declarative Weaknesses
The fact that declarative languages are often easier to use, often become their weakness too. Often you start out writing simple logic in your declarative language, but as your abilities and requirements grow, your programs become more and more demanding. And sooner or later you hit the declarative wall: You find yourself in a situation where you need to specify how something is to be done. The what is no longer enough.
The reason you often hit the declarative wall is exactly that these languages are often designed to solve a very specific problem. The language designer did not think of everything, and you hit the wall when you need something the language designer did not think of. In fact, if the language should enable you do solve every problem under the sun, it would end up being pretty close to an imperative language.
When enough users of a declarative programming language hits its declarative wall, the result is usually that the declarative language is extended with features that support the needed use cases. Sometimes this can be done elegantly. Often, however, the concise syntax of the given declarative language starts to break down. The language starts becoming more complex than an implementation in an imperative language would have been.
SQL windowing functions is a great example. Imagine you need to iterate over a result set and calculate the difference between values of different rows. This was not possible in SQL until windowing functions were added (2003). The SQL windowing function syntax is not pretty. Such operations could most likely have been expressed almost as concisely, and probably be more readable using many popular imperative languages.
There are still many operations on data that you cannot easily do with SQL. For instance, to my knowledge you cannot combine reads and writes into a single, atomic operation. This is beyond SQL's declarative wall. To solve these kinds of problems, many databases support stored procedures. Stored procedure languages are often imperative, but can call the declarative SQL from inside the language.
If we had just started with an imperative query language (IQL) from the beginning, it would have been possible to express data manipulations more freely from the beginning. Imperative languages evolve too, to make them easier and more conciset to use, but at least you could do pretty much anything even with the first versions of most popular, imperative languages. It might have been more verbose, but at least you didn't hit a wall.
Additionally, with an IQL, stored procedures would be a natural part of the language instead of something each database vendor implements on their own, in different versions.
Shameless plug: At vstack.co we are exactly creating a database that uses an imperative query language instead of SQL.
You see the same phenomenon with CSS. Expressing CSS styling for bigger web applications have become so verbose that developers have started using CSS pre-processors like LESS and Sass. Why? Because developers felt they need features like inheritance, variables, calculations etc. Yes, these features are still reasonably easy to express declaratively, so they could perhaps be added to CSS. But sooner or later the CSS syntax may break down too. I, for one, will be experimenting with using JavaScript to define CSS styles in the future. Just to make sure I have the imperative features if I should need them.
Most Imperative Languages are Also Declarative
Many popular programming languages are classified as imperative even though they are also partly declarative. This includes the popular languages C, C++, C#, Java, JavaScript, Scala, Ruby and Python. I will explain what I mean below.
The following JavaScript code calculates the interest of capital over 5 periods (typically years):
var capital = 100; var interest = 0.10; //10% per period var periods = 5; var result = capital; for(var i = 0; i < periods; i++) { result = result * (1 + interest); }
This code is clearly imperative. The calculation is performed using a series of steps. After the code has been
executed the result
variable will contain the result of the calculation.
Would it not be easier if the above calculation could just have been expresses declaratively? Something like
result = capital with interest : 100 0.1 5
With a very simple "trick", it actually can. Well not exactly like above, but close enough. Look at this JavaScript code:
function capitalWithInterest(capital, interest, periods) { var result = capital; for(var i = 0; i < periods; i++) { result = result * (1 + interest); } return result; }
Yes, that is right! A simple function definition! Now I can express the result of an interest calculation declaratively like this:
var result = capitalWithInterest(100, 0.1, 5);
JavaScript function definitions are imperative, but function calls declarative. Functions separate the what from the how. When you call a function you usually don't care how it is implemented. You just want the job done. That is declarative.
Fantastic, isn't it? And even better - pretty much any modern programming language support functions. At least the ones I mentioned earlier.
In fact, pretty much any kind of abstraction mechanism we use in our modern languages serve a similar purpose. Abstractions separate the what from the how. They separate interface (what) from implementation (how). This is true for functions / methods, interfaces, classes and objects, and for bigger APIs as well (e.g. Java IO, JDBC, your own APIs etc).
JavaScript also contains a declarative way to express state. Look at this:
var myObj = { field1 : "val1", field2 : "val2" };
This example uses the JSON notation to configure an object with fields. JSON is declarative, but you can use JSON from inside JavaScript.
The imperative way of configuring the object above would have been the following stepwise actions:
var myObj = {}; myObj.field1 = "val1"; myObj.field2 = "val2";
Some programming languages also enable you to express XML declaratively, similarly to how objects are expressed in JSON.
State vs. Actions
Declarative languages tend to be very good at expressing state (data). For instance, HTML expresses a document structure, CSS expresses styling properties on the document structure (styling is state), and JSON expresses object structures. They all do what they do reasonably well, as long as what you are trying to do is pretty straightforward (good - not perfect).
Because declarative languages are good at expressing state it is often tempting to use a declarative language or approach when generating state. For instance, to use some kind of HTML variant when you are generating HTML from data in a database. JSP (JavaServer Pages), ASP and also AngularJS is based on this idea.
However, generating state based on data (generating data based on other data) is an inherently imperative process. It may be easy to express simple data generation with a declarative approach (e.g. JSP standard tag libraries or AngularJS directives), but as soon as your data generation becomes more complex (and sooner or later it always does), the declarative approach typically breaks down.
Declarative languages are great at expressing state, but imperative languages are better at expressing actions.
Summary
Declarative programming languages are different from imperative languages, but they are not better. If you hear developers say so, they are suffering from the declarative delusion. Point them to this article to cure them.
Declarative languages are usually designed for a specific domain. As long as the domain logic is simple, declarative languages can be very concise and easier to learn for non-programmers. As the domain logic gets more complex, however, an imperative language is typically more appropriate.
Most popular programming languages which are categorized as imperative, also contain declarative elements. Used properly, many such imperative languages can actually mimic declarative languages pretty well, while at the same time provide much more freedom to divert from the original intentions of the declarative language.
Yes, there are many cases where a declarative approach is simpler than an imperative approach, but you can often mimic a declarative approach with most imperative languages. Some languages, like Scala, are even designed to be able to let you define DSLs easily which is translated into simple objects and function calls.
Tweet | |
Jakob Jenkov |