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.