In a nutshell
My first impulse was to agree with D.W.'s answer that the two concepts
are orthogonal. On second thought, I think it is only partially true,
and I will also try to argue that they are two sides of the same coin.
In first approximation, declarative programming just specifies what
you want and lets the system find how to get it, while imperative
programming gives precise instructions to the system that ensure you
get the result you want. In practice, you usually have a mix of both. You
may add hints to your declarative programming so that the system can
better find how to do it. Conversely imperative programming often
includes high level concepts that let you skip over some details
that are automatically handled by the system.
Since encapsulation helps you increase the number of high level
concepts that you can use just as needed, it is quite fair to consider
that encapsulation also supports a more declarative form of
To put it another way:
Declarative programming assumes that the compiler/interpreter will include
something like a theorem prover or a solver that will find how to do
it from a specification of what is to be done.
Encapsulation is a way, for some predefined cases, to have the proofs
already prepared and applied as code in the encapsulation. The
specification of what is to be done hiding the internal description of how to do it.
Denotational semantics does not make a difference between $3+4$ and $7$. Should we then consider that there is much difference between a proof and code to be done and a canned proof and code already done?
In that respect, one can consider, as does the author of the question,
that encapsulation has strong similarities to declarative programming.
But this is also to a large extend a matter of style, and of
interpreting what is written.
BTW: I do not like very much the expression "declarative syntax". The
issue is very much a semantics issue. "Declarative style" would be a
more appropriate expression.
To better explain my understanding of declarative programming, I will try to use, as much as I understand it and somewhat
loosely, the Curry-Howard isomorphism. One basic idea of this
isomorphism is that it describes a correspondence between programs and
proofs, and between program specifications and theorems.
So a program may be read as a (constructive) proof of its
specification. Conversely, given a mathematical statement with some
existentially quantified variables, you can turn it into a theorem by
providing a proof, and if you limit yourself to constructive proofs,
you can extract from that proof a program that actually tells you how
to make the statement true.
For example if the statement is $\forall a,b,c \in \mathbb C,\; \exists
x\in\mathbb C,\; ax^2+bx+c=0$, you can extract from the proof an
operational way of solving second degree equations. In some cases, the
proof may work only under some conditions, which will be conditions
you impose on the data for the program to run.
BTW The word algorithm comes from the name (of the home town) of a
Persian mathematician who was solving equations. It is all consistent.
Some research on automated program generation consists precisely in
taking the specification of a program as a theorem to be proved, in
some appropriate formal theory. A proof assistant will record all the
formal steps of the proof, which can be partially automated, and then
the program meeting the specification is extracted from that proof.
Ideally (sometime in the future in most cases), if the whole process
is fully mechanized with a theorem prover and does not need human
intervention, you can just specify your program, as use the system to
compile the specification into running code.
This approach is the ultimate declarative programming: you describe
what you want, but not how to get it. An you can leave it to some kind
of deductive system the task of determining the steps to achieve the
This is used in practice, not so much to mechanically generate complex
programs, but to handle automatically simple problems without
requiring the programmer to specify all the operational steps.
To take a trivial example, the steps for bootstrapping a compiler
written in its owns language with an interpreter can be found
automatically by a small logic program. And computer science has many
other examples, in very specialized area where it is tedious, but easy,
to determine the steps needed to achieve a result.
The principle of encapsulation is to take some meaningful subpart of a
program, and represent it by an interface which actually specifies it,
so as to hide the way it is actually implemented. The specification
includes precise interfaces (API) to use the encapsulated code, but
may also include other information, assertion, or formal mathematical
statement about the API, that fully specifies what it is supposed to
do, and possibily some pragmatic properties regarding performance, or
even some assertions about complexity. Of course, such full
specification are not so common.
This has several purposes. It gives a precise specification of part
of a program, abstracting away the implementation details so as to
simplify reasonning by considering only high level concepts and
properties defined in the interface. It allows easy reuse of the
encapsulated code in other program. It allows making improvements to
the encapsulated program without affecting the larger programs that
use it, as long as the specification is respected by all. And more
... but let's stay on topic.
Once you have encapsulated some concepts, you can use the
encapsulation in any program so as to have the concepts available for
programming without having to tell more about the details of their
implementation. Note, in passing, that the high level language
compiler also provides that kind of service.
If you see your program as a proof, it becomes a simpler proof, with
less details, thus closer to the specification it proves. So in this
sense, the encapsulation helps you to be more declarative. You use
only its specification, and forget the implementation which is only a
canned proof of that specification (so you can forget the theorem
Now, the API provided by an encapsulation may be seen as more or less
declarative, as explained by D.W. depending on whether the primitive
operations it provides seem to be more specification of what should be
done, or low level operational steps.
The distinction is important, as trying to be more declarative may
have a impact on the properties of your program (readability for
example). But it is also very much in the eyes of the beholder.
By nature, an encapsulation will be somewhat more abstract, higher
level, that what it is implemented with. In that sense it is
necessarily more declarative.
Another point is that asking for an equation to be solved is a very
declarative kind of thing to do. But how different is the call
solve2(a,b,c) to solve a second degree equation from the call
matrix3(a,b,c) to make a 3-dimensional matrix, which may be seen as
Programmers tend to consider that thing are more declarative when
using logical rules, constraints, equation, that have to be applied or
resolved, rather than computation steps to be executed.
But even these distinctions are not always clear cut.
My favorite example is context-free grammars. Most people learn to see
them as rewriting systems, which are a rather operational kind of
object. All the words are generated by rewriting other words. But a
context-free grammar may equally be seen as a set of language
equations, which is a very declarative kind of thing.
Note that this answer in not in contradiction with D.W.'s answer. But
I do believe that your initial concern was well justified. There is a
matter of style, of feel (says D.W). But there is also a matter of
level of discourse. The higher the level, the less you need to prove,
and the more declarative you are. And encapsulation helps that, by
providing high level definitions. That was my reason for bringing in
the Curry-Howard isomorphism and automated programming.