
Declarative Programming: Is It a Real Thing? (2015) - akkartik
https://www.toptal.com/software/declarative-programming
======
alipang
Instead of talking about declarative programming we should talk about
denotational programming - writing programs that map to a consistent mental
model.

The fact that something is declarative doesn't mean that it's understandable.
Webpack for instance is a piece of technology I consistently struggle to
understand and predict the behaviour of, even though it's declarative. (Not
intended to shit on webpack's developers / maintainers in anyway.)

------
tel
"Declarative" programming has become something of a buzzword. This is
unfortunate because it prevents finer distinctions between domain specific
language techniques.

I think it achieved its buzzword status because it for a long time was
something people clearly _recognized_ but couldn't define. Fortunately, Neel
Krishnaswami wrote a very cogent post putting his finger on the nerve.

[https://semantic-domain.blogspot.com/2013/07/what-
declarativ...](https://semantic-domain.blogspot.com/2013/07/what-declarative-
languages-are.html)

With this definition we can see that (a) the author misses the boat on
"declarative" in a concrete way and instead lands on something much closer to
DSLs in general and (b) then only uses a narrow set of DSLs to provoke
criticism and thus presents a very weak argument.

The powerful core of his argument—the "unfolding" principle—is not something I
can claim to understand fully from this article, but it suggests something
worrying: that every DSL must transparently resemble its interpretation. This
makes it easy for a user of the DSL to recognize how it will be interpreted
and thus achieve ease of understanding... but it gets dangerously close to
banning abstraction all together!

In my opinion, the true measure of a DSL is the consistency and diversity of
_semantic interpretations_ (or "models") that are valid for the DSL and
consistent with one another. This measure seems to establish the resilience
and leverage a given DSL's topic might have.

Consider how SQL can describe set operations, an abstract search process, a
logical predicate, and also a concrete set of searches achieved across
physical data stores.

Anyway, to answer the question in the title—something the article neglects to
do—I'd strongly suggest that "yes" declarative programming is a real thing,
"yes" DSLs are real too, and "no" they do not automatically fall to the
problems one can potentially extrapolate from by getting frustrated at
templating solutions.

~~~
drudru11
I cannot quite decipher Neel’s interpretation. The math syntax on his site
isn’t rendering. Can you provide a simple explanation? This is interesting.

~~~
tel
Let me first transcript his math. He's giving a judgement for regex semantics
`s matches r`.

    
    
        - [for all r] <empty> matches r [unconditionally]
    
        - [for all c] c matches c
    
        - [for all w, r1, r2]
          IF w matches r1 AND w matches r2 
          THEN w matches (r1 \/ r2)
    
        - (no rule for "w matches bottom")
    
        - [for all w, r1, r2] 
          IF there exists w1, w2 such that w = w1 . w2
          AND w1 matches r1 AND w2 matches r2 
          THEN w matches r1 . r2
    
        - [for all w, r]
          IF there exists w1, w2 such that w = w1 . w2
          AND w1 matches r AND w2 matches r*
          THEN w matches r*
    

So he notes that in the semantics of regex we have non-trivial "there exists"
quantification expressing the "search" over possible breaks in the target
string.

This need for an existential to give semantics to a language is his focus.
Perhaps the most obvious example then is Hilbert's epsilon, such as what
appears in TLA+

(section 6.6)
[http://lamport.azurewebsites.net/tla/xmxx00-02-09.pdf](http://lamport.azurewebsites.net/tla/xmxx00-02-09.pdf)

This is called CHOOSE in TLA+ and it's a constructive existential quantifier
in that

    
    
        a / b === CHOOSE c in REAL : a = b * c
    

results in evaluating a / b to "some" real value c such that the
multiplicative condition holds (or some arbitrary value if no such value
exists, fwiw).

This shows up in SQL as being essentially exactly what SELECT is doing:
`SELECT * FROM Table WHERE Cond` is

    
    
        CHOOSE row in Table : Cond(row)
    

except now CHOOSE returns all matching values as opposed to just "some"
matching value.

------
olooney
Not this again. Haskell, Prolog, CSS, SQL are classic examples of declarative
programming, but really, what isn't? Everything except machine code is to a
certain extent declarative.

Obviously, even the C compiler is free to optimize and rewrite your code to a
large extent. Maybe it will put everything in registers! Maybe it will shuffle
variables out of memory! Maybe it will skip entire instructions, or unroll
loops! Maybe it will completely eliminate code that it believes to be dead!
Maybe it will use SIMD instructions to do four multiplications at once! Will
it will pad your structs, or change byte order? Will it use 16, 32, or 64 bits
for you integers? There's no way to know! Even if you figure it out for your
own machine today, the code you write today may be compiled to target a
different architecture in the future. Obviously, then, C is a declarative
language because the programmer only provided a high-level outline of what he
or she wanted and let the compiler figure out the details.

Even at run time, we find that many system calls are declarative for all
intents and purposes. What happens when you call malloc()? Well, you tell it
what you want, and a memory manager decides how to give it to you using a
variety of memory management strategies: maybe they call mmap() or
VirtualAlloc() or some other syscall, or maybe they just give you a pointer
into a region of memory they allocated earlier and have been hanging onto.

Is even machine code fully imperative? Maybe once upon a time it was, but now
with all these caches and pipelines and branch prediction, it seems like even
the hardware is making decisions on our behalf about how to "best" run a
program.

The way I see it, we can either acknowledge that these computer thingies are
in fact to a large extend "doing what we mean" rather than following our
explicit instructions and accept the corollary that we are never _quite_ fully
in control, or we can all go back to writing assembly for the 6502.

~~~
tabtab
Laynes Law:

Every debate is over the definition of a word.

Or every debate eventually degenerates into debating the definition of a word.

Or once a debate degenerates into debating the definition of a word, the
debate is debatably over.

~~~
wnmurphy
What do you mean by 'over'?

~~~
tabtab
"Pointless to continue", I would assume. I've been in looong debates that end
up being tied to the meaning of a word. Our worldviews often shape the way we
use and apply language. For example, how one defines "type" may ultimately
pivot on how a given person processes categories in their own mind. I've
probably spent a novel's worth of text in online debates about "type".

~~~
notanon0
I believe he was being facetious to cement the point.

~~~
tabtab
Okay, thanks. I missed that one.

------
nine_k
As a side note, everyone here likely knows the most successful declarative
language, SQL.

It's domain-specific, but within its domain, it works quite well.

~~~
deckar01
Is SQL a declarative language? It requires predicates for everything. Even
declaring a schema requires a predicate, which doesn't seem any different than
function arguments to me. "select" abstracting the underlying DB engine's
"control flow" doesn't seem characteristically declarative. It seems like the
same type of standard library that any imperative language would offer for
convenience.

Edit: Apparently functional programming is declarative.

~~~
empath75
If it's not, then nothing is.

~~~
deckar01
Apparently functional programming is considered a form of declarative
programming. That doesn't make much sense to me, but I guess relative to
writing assembly or C it is declarative. The distinction seems arbitrary,
because even C and assembly contain abstractions for more complex operations
taking place in the OS and in the hardware.

~~~
rgoulter
A cliche example is "squares of values".

imperative code would be something like:

    
    
      fn squares(array A)
        array B;
        for (idx, x) in A {
          B[idx] = x * x
        }
        return B;
      }
    

(or "B.add" or some other mutating action). whereas 'declarative' code
'declares' what it wants:

    
    
      fn squares(array A) {
        return A.map { |x| x * x }
      }
    
    

I think "declarative" is used more in terms of "declare the result you want,
not how you'll get it". It's easier to focus on what the output of the program
is with declarative code.

Higher-level abstractions are _probably_ necessary, but I don't think it's
fundamental to how I'd use the term. :-) Although I wouldn't be surprised to
see a variety of ways people use the term.

~~~
jtms
Is that example code elixir?

~~~
rgoulter
It's not anything in particular.

Rosetta Code has "apply a callback to an array" example with Elixir.
[https://rosettacode.org/wiki/Apply_a_callback_to_an_array#El...](https://rosettacode.org/wiki/Apply_a_callback_to_an_array#Elixir)

    
    
      Enum.map([1, 2, 3], fn(n) -> n * 2 end)
      Enum.map [1, 2, 3], &(&1 * 2)

------
mahidhar
An interesting observation in the article is the data structures DSL that the
author goes into quite a bit of detail, which is essentially what most
Clojure/Lisp developers also talk about.

Basically the author points out a lot of benefits of using the native
serialisable data structures of the language itself to model the domain,
instead of creating a separate representation which has to be parsed,
optimised, evaluated, etc. Lisp users just take the same idea one step further
by having the "host" language also be represented using the same data
structures, instead of just the DSL's.

As Alan Perlis said, "It is better to have 100 functions operate on one data
structure than 10 functions on 10 data structures."

------
tabtab
Re: _Arguably the most successful declarative programming tool is the
relational database (RDB)._

I used to use database systems that were mostly imperative. While they do put
more onus on the app developer to manage "machine efficiency", such queries
were easier to build and study (debug) incrementally, including the machine
run-time profile.

Imperative gives you a better chance at fractal divide-and-conquer of tasks
and sub-tasks that can be tested and analyzed as independent units. SQL has
made that relatively difficult. Views and the newer WITH clause help, but they
are still not fractally dissectable within themselves.

There are pro's and con's to each, but I'd like to see the world forked around
1982 into SQL-World and Imperative-DB-World and see which actually does
better.

The theoretical benefits of declarative queries being more machine optimize-
able is exaggerated when put to practice. If it were true, we wouldn't need
DBA's to rewrite slow SQL. Imperative can also be machine optimized.

Declarative can be quite useful for GUI's but only if "events" or equivalent
can imperatively tweak the results along the way "between" steps. Declarative
may do 80% of the work, but certain operations will need custom tweaking,
usually with imperative code.

Optional events added to declarative systems to adjust stuff along the way is
a good mix of imperative and declarative: yin and yang: declarative for the
common "grunt work" patterns and attribute management, and imperative for
custom adjustments. (If the system is designed to integrate them well.)

------
marcosdumay
Well, since the author asks so nicely, let me answer. I do not consider that
code to be elegant.

Just count how many possible errors you can make on a simple templating DSL,
and then count how many errors you can make on that dsDSL one. One is clearly
better than the other.

I would say that all the problems enumerated there come from a bad selection
of DSLs to study. It's perfectly understandable that the author chooses
templating languages, and I don't know of any better templating DSL to put
there, but that only evidences that we lack an actually good templating DSL
(outside of the scope of Tex). That just shows that text templating is hard,
not that DSLs are inherently problematic.

For evidence that the problem is not with the concept of DSLs itself, just try
to write the same article using SQL as an example. You can't.

------
skybrian
The author sort of hits on the complexity of writing an external DSL. Even
good DSL's like SQL have complicated implementations and are usually treated
as a black box. Users don't learn them by reading the source code or stepping
through an example in a debugger.

This means you need to treat your fellow developers as end users rather than
co-maintainers. You need a good spec, plenty of examples, and probably
language-specific debugging tools, which have to be taught as well.

No wonder people look for lighter approaches like macros or internal DSL's
(which are often just libraries with funny calling syntax). Reusing existing
language tools is often the way to go.

------
empath75
When I think of a declarative DSL, I think of something like cloudformation or
HTML. The main advantage of a declarative language is that you can describe a
desired end state without being overly concerned about the implementation
details of arriving there.

I think the sorts of dsl's that he's complaining about are more hybrid
approaches and not fully declarative. I think Chef is an example of a DSL that
does that right, in that if you have the right plugins and helpers you can do
it fully declaratively, but it's fairly trivial to add in blocks of fully
imperative code or define new blocks.

------
zokier
In a very twisted sense even C could be described as a declarative language. C
code describes the observable effects under the abstract machine model, and
the compiler tries its best to generate machine code that matches those
effects. But more importantly the compiler is completely free to ignore the
exact "imperatives" that the programmer provides, as long as the defined
outcome is the same.

------
mpajunen
The article makes a valid argument against bad DSLs. But the dsDSL is also
declarative so it's not really an argument against declarative programming.
Similarly using a native data structure for a SQL query doesn't make the query
any less declarative (and can have a lot of advantages).

The dsDSL is actually pretty close to how React or some functional languages
work with HTML.

------
Paddy3118
"Is SQL declarative?"
[https://softwareengineering.stackexchange.com/questions/2003...](https://softwareengineering.stackexchange.com/questions/200319/is-
sql-declarative)

~~~
tabtab
Re: _" Shouldn't an SQL engine not care about if you used IN, EXISTS, or JOIN
if it is truly declarative shouldn't it just give you the correct answer in
reasonable time if possible by any of the three methods?"_

I don't see this as evidence SQL is not declarative. Declarative doesn't mean
there must be only one way to do (request) something, and it doesn't mean the
optimizer is required to be well-written in practice. Maybe in some RDBMS it
does process all 3 fast. That's not a characteristic of SQL the language.

------
pkphilip
Red language is, to an extent, a declarative programming language - especially
for the UI.

------
amelius
CSS seems to be a real thing ...

------
austincheney
I am not a huge fan of the declarative model for much of the same reasons I am
not a fan of OOP. Both concepts are full of convention and ritual to decorate
code in a way that is easy to understand in isolation and convenient to put
together. That sounds great except rarely does anything actually work in
isolation and the word _complex_ literally means _put together_.

Personally I am a big fan of functional imperative. Write instructions to
solve the problem as directly as possible with the fewest decisions/statements
as possible. Isolate the solution into a function that is referenced when
needed. Opposite of complex.

~~~
taffer
I think you're confusing things. (Pure) functional programming is a form of
declarative programming. The most important feature of declarative programming
is the elimination of side effects and the idea of referential transparency.
It has nothing to do with OOP.

~~~
austincheney
> I think you're confusing things. (Pure) functional programming is a form of
> declarative programming.

I disagree. See [https://www.red-lang.org/p/about.html](https://www.red-
lang.org/p/about.html) as an example of a language that is purely imperative
functional.

~~~
taffer
Imperative and functional are opposites. There is no such thing as a purely
functional imperative language.

~~~
convolvatron
imperative programming is control flow centric. do this, do that, do this
other ten times.

functional programming is about referential transparency - the value of an
expression is determined entirely by its arguments and not some hidden state.

so you certainly can have imperative functional programming which is both
recipe-like and referentally transparent.

declarative programming is the antipode of imperative programming. yes, its
referentally transparent, but it also attempts to describe the nature of the
solution, rather than the (imperative) process to get there. i dont know that
there is a declarative variant that makes sense without transparency...because
if there is hidden state and we have no handle on order of evaluation, its
kind of a lost cause.

~~~
madhadron
The usual definitions of imperative vs functional are that imperative is built
from Hoare triples and functional is built from lambda calculus. By those
definitions, you cannot have a functional, imperative language.

~~~
austincheney
When used properly the nested scope provided by lambda calculus allows shared
variables accessible from outside a local function thereby accounting for the
precondition of Hoare's triples without it being locally present or passed in.
This is enough to satisfy both the functional nature of portablity/isolation
and the imperative model by Hoare's logic.

* [https://en.wikipedia.org/wiki/Hoare_logic](https://en.wikipedia.org/wiki/Hoare_logic)

* [https://en.wikipedia.org/wiki/Imperative_programming](https://en.wikipedia.org/wiki/Imperative_programming)

* [https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexic...](https://en.wikipedia.org/wiki/Scope_\(computer_science\)#Lexical_scoping)

~~~
madhadron
> When used properly the nested scope provided by lambda calculus allows
> shared variables accessible from outside a local function thereby accounting
> for the precondition of Hoare's triples without it being locally present or
> passed in.

Yes, you can express any Turing complete system in terms of any other Turing
complete system.

> This is enough to satisfy both the functional nature of portablity/isolation
> and the imperative model by Hoare's logic.

I think you're missing the point. It's not about portability or isolation.
It's about the mathematical system you use as the foundation of your
semantics. Yes, you can implement a lambda calculus in turns of Hoare triples
and you can implement Hoare triples in terms of lambda calculus. Or in terms
of unification over Horn clauses or combinatory logic.

What makes a language functional is that its semantics are defined as an
extension of the lambda calculus. What makes a language logical is that its
semantics are defined as an extension of unification over Horn clauses. All
the benefits people suggest to using one style or another are theorems, not
definitions.

~~~
austincheney
> It's about the mathematical system you use as the foundation of your
> semantics.

I think you are conflating lexical scope with functional programming. Lexical
scope applies even without functions, but functional programming requires
functions. The difference between a scoped block and function is that
functions have identity and the scoped block does not even though both benefit
fully from lexical scope.

What makes a language functional is whether components are composable via
function. That could mean several different things, such as: lexical scope,
first class citizens, recursion by reference, callbacks, and so forth. A
simple way to thinking about it is things are well separated, sometimes
atomically and sometimes in a taxonomical fashion, but that separation has
identity. That is very different than an OOP model where things are extended
and put together and that togetherness or extension has no identity.

