
Imperative vs. Declarative - philip_roberts
http://latentflip.com/imperative-vs-declarative/
======
hackinthebochs
I'm not sure I agree with some of the examples in the article. The examples
used paint declarative programming as basically abstractions over details. The
problem is that there is no line where an abstraction crosses the boundary
into declarative programming. It's not really about abstractions but about
control flow. If your code has a specific order it has to run in, then its
imperative as you're still describing the steps needed to perform the action.
SQL is declarative because you're describing the output set rather than the
steps to generate it. Functional languages are considered declarative because
of the fact that pure functions can be rewritten, optimized, lazy evaluated,
etc by the runtime. I have a hard time considering map/reduce/etc in isolation
as examples of declarative programming, as they're usually used in conjuction
with an algorithm that most definitely has a defined execution order.

~~~
camus
so what would be declarative programming ? does it even exist? i mean, at some
point you need to write some logic , and logic is imperative. Let's take a
html file. It is declarative. but the underlying logic is written somewhere
else. so you cant really have pure declarative programming? if it is possible
how ?

~~~
stonemetal
An evaluation strategy is imperative but the logic that the strategy operates
on doesn't have to be.

~~~
camus
great point !

------
MattRogish
I really like SQL. Sure, the language has warts but the ability to concisely
represent WHAT you want, not HOW you want it, makes it very readable once you
understand the simple constructs and how to properly design tables and indexes
(not very hard).

For example, consider the problem of finding the second largest value in a
set.

In SQL, I'd do something like:

    
    
      SELECT MAX( col )
        FROM table
       WHERE col < ( SELECT MAX( col )
                       FROM table )
      

It's pretty readable, and can almost be read in plain english: "Get the next
biggest value from the table where it's smaller than the biggest value."

How might you do this in Java?
[http://stackoverflow.com/questions/2615712/finding-the-
secon...](http://stackoverflow.com/questions/2615712/finding-the-second-
highest-number-in-array)

But look at all the other ways you can do it in that thread. None of them are
very readable. And, they can hide subtle bugs that you won't find just by
reviewing the code.

Ruby has a pretty concise example if you happen to know the trick, and that
the sort performance isn't miserable (kind of a gotcha question):
[http://stackoverflow.com/questions/8544429/find-second-
large...](http://stackoverflow.com/questions/8544429/find-second-largest-
number-from-an-array-in-ruby)

This is a very simple example, but as you scale up to more complex problems I
almost always find SQL is fewer lines of code, more readable, and far less
buggy (SQL tends to either work or not work - I find much more subtle bugs in
a 30 line Java algo than a 10 line SQL command).

~~~
RyanMcGreal
Aside: Python is similar to Ruby, albeit using the sorted() function rather
than the sort() method.

    
    
        sorted(vals)[-2]

~~~
textminer
The pedant in me squirms at seeing a quadratic-time solution to something so
linear. Use a heap or a scan, sir!

~~~
textminer
Oh, now I have to yell at myself. No doubt Python's sorted is some n*logn
quicksort, and not actually in quadratic time. Crow for all!

~~~
masklinn
It's an adaptive mergesort-based sort: <http://en.wikipedia.org/wiki/Timsort>

------
octo_t
Prolog is declarative programming take to the maximum (excluding things like
Answer Set Programming/clingo etc).

In Prolog you _ask_ questions. For example: subset([1,2],[2]).

then it goes away and says "yes". Or you want to know if any subsets exist:
subset([1,2],B).

B = [] B = [1] B = [2]

This makes it really really nice for some surprising tasks (Windows NT used to
ship with a prolog interpreter for setting up the network)

~~~
philip_roberts
Ooh, I forgot all about prolog!

I worked through the 7 languages in 7 weeks book, and solving a sudoku with
prolog blew my mind. I think the first "real" programming I did was a sudoku
solver in Excel and VBScript (yeuch).

~~~
octo_t
Prolog is really awesome (and impressive) for solvers where there is an
"optimum" solution for what you want.

Its fairly trivial to write a checkers AI in prolog if you can define _what_
you want + need:

------
icebraining
Map and other functional constructs may be declarative, but I only "feel" like
I'm programming declaratively when I'm coding in a language like Prolog.

The fact that, with unification and backtracking, you can not only get a
result for a query, but also "pass a variable" as an argument and get a
possible value makes it seem much more like a mathematical expression and less
like a computation.

For example, I can define some relations:

    
    
      parent_of(john, mary).
      parent_of(mary, eve).
    
      grandparent_of(X, Y) :- parent_child(X, Z), parent_child(Z, Y).
    

And then I can simply run a query:

    
    
      ?- grandparent_of(john, eve).
      Yes
    

But I can also make it fill in the value for me:

    
    
      ?- grandparent_of(john, X).
      X = eve
    

'grandparent_of' is not some piece of code, it's an actual declaration of a
relation between the terms.

Of course, you can do unification and backtracking in other languages, but
Prolog is designed for it.

------
PeterisP
On the flip side, it also drastically changes the typical errors.

In imperative style, most of your mistakes or carelessness will usually mean
that the machine makes a wrong result or crashes in the process - a bad
'what'.

In declarative style, most of your mistakes or carelessness will usually mean
that the machine will take a bazillion times less efficient way trying to make
that result, possibly taking 'forever' or running out of memory - i.e. a bad
'how'.

~~~
JoshTriplett
I've found in declarative style, most mistakes just turn into compilation
errors.

That said, the "why did it choose that terrible implementation?" problem does
occasionally come up in declarative programming, and inherently never comes up
in imperative.

------
Uchikoma
I don't think the author gets declarative right. It feels like he bolts a cool
word onto some things he uses. Call me old fashioned, but I think Prolog is
declarative, map() and reduce() are not.

------
hermannj314
Lately, when I code in C#, I write the code I wish was possible with the goal
of trying to code to the problem as stated in the requirements. This way the
code that solves the problem looks almost exactly like the description of the
problem. That is step #1.

Step #2 is doing whatever is necessary to make that code work. Sometimes this
means using the more interesting stuff like reflection, dynamic objects,
expression tree visitors, etc. but I find that subsequent issues keep getting
easier to deal with. This is because step #1 is naturally building a DSL for
your problem domain and you start to find that what you did in step #2 is
quite reusable.

I've been programming for a while, so I have experience with the imperative,
"write the code that solves the problem" approach and it works too, but I am
having fun with the "write the code that describes the problem" approach more.

Just my two cents.

~~~
hackinthebochs
This is what I love about C#, it really provides all the necessary components
to do this style of "wishful development". I've started doing this everywhere
and the results are great. Like you said, the code itself literally reads like
a specification. It's allowed me to turn what is otherwise would have been an
extremely tedious web application into something I enjoy working on.

As an example, I turned what otherwise would have been an extremely tedious
exercise in writing tons of obscure SQL (creating reports from a very non-
standard database layout) into an API for creating reports that is literally
like reading the specification for the report. And all of it was done in about
250 lines of C#. And to top it off we still have complete static type
checking! I really cannot sing the praises of C# enough.

------
taeric
It is not just as programmers. Consider, most cookbooks. Then consider the
directions that come with Ikea furniture. Of course, the real beauty of both
of those examples, is that they are a mix of declarative and imperative
instructions.

For some reason, it seems we programmers are adamant that it must be one or
the other. Consider all of the examples, either purely imperative or purely
declarative. Why not both?

~~~
hcarvalhoalves
Ideally, we would all write programs by assembling declarations, imperative
code would be limited to internal implementations. That's largely the reason
it's good practice to abstract away implementation behind APIs - what you have
left is almost a pure declarative language, or DSL, that maps 1:1 your problem
domain, without looping or branching or I/O (which are computation details).

Taking the example from the original article, it would be more akin to:

    
    
        // Implementation
        function double(n) {
          return n * 2;
        }
    
        // Declaration
        [1,2,3,4,5].map(double)
        => [2,4,6,8,10]

~~~
taeric
And, see, I would actually invert further. The declaration should be:

    
    
        elements = [1,2,3,4];
        doubledElements = doubleElements(elements)
    

Basically, if you see the words map, fold, reduce in your code, you are
probably not as easy to understand as you'd like to think.

Of course, in the cooking methaphor, I'm ok with mutating elements and just
doing:

    
    
        elements = [1,2,3,4];
        doubleElements(elements);
    

This clearly has issues if multiple "cooks" are working with elements. But is
ridiculously easy to intuit regardless. (Precisely because in real life so
many things are changed by imperative commands.)

~~~
hcarvalhoalves
> And, see, I would actually invert further. The declaration should be:

But then you lose pureness, right? The whole point of using high-order
functions is allowing you to be as declarative as mathematics, so you can just
operate functions together.

Consider that in the first example, I only need to write the implementation
for doubling a number n, while the `doubleElements` implementation is too
specific, would throw the other half of the code back into imperative land.

~~~
taeric
Only in my example that relied on mutations. The first can all be implemented
with pure functions just fine. Indeed, I was assuming it would be, hence the
assignment to a new variable.

I suppose I should have said that the doubleElements implementation would
likely be that map one liner. (Though, it needn't be. One could exploit custom
knowledge of the domain there to do crazy crap like memoize the calls.)

That make sense?

~~~
hcarvalhoalves
> Only in my example that relied on mutations. The first can all be
> implemented with pure functions just fine. Indeed, I was assuming it would
> be, hence the assignment to a new variable.

Yes, the first example uses pure functions, but I guess you're confusing pure
functions with HOFs [1]. The point is only having to write the implementation
to double one number, and extrapolating it by composition. Consider that in
your example, for instance, you would need a `doubleHash` function for hashes,
and so on.

<http://en.wikipedia.org/wiki/Higher-order_function>

~~~
taeric
I did not realize we were debating HOF versus pure functions.

That is, I'm fine with using both the pure and HO functions. I just think
hiding the HOF ones behind a normal function call is usually a big win for
readability.

So, to do the full example:

    
    
        elements = [1,2,3,4]
        doubledElements = doubleElements(elements)
        function doubleElements(e) {
            function double(n) { return n*2; }
            return e.map(double);
        }
    

Where I would assume the "reader" code would only have the first two lines.
The rest would be behind the implementation layer. If the double function
would be used elsewhere, no need to scope it to doubleElements.

------
toki5
Great article, but one thing that's sort of glossed over here, and that I
half-disagree with, is this:

>But we also get to think and operate at a higher level, up in the clouds of
what we want to happen, and not down in the dirty of how it should happen.

The author mentions this at the end, but I feel it should be stressed more
strongly: The dirty of how is _important_. The author presents a big "if"
here, which is: _if_ the function we've built to abstract away some ugliness
performs in the best, most efficient way possible, with no drawbacks, then,
yes, abstracting that functionality away and forgetting it is okay.

But to me that's a big if. It is just as important to me to understand and
recognize that _map_ is fast, efficient, and to understand _why_ it's fast and
efficient, so that someday, if you come across a situation where _map does not
apply,_ you will know why, and you'll be able to use something better.

Being up in the clouds all the time is, to me, a pipe dream -- we must always
be cognisant of the ground on which we built this tower of abstraction layers.

~~~
saraid216
The fact that _map_ is fast and efficient isn't really its selling point to
me, though. It's that it's a simple _concept_ , so I use it when the _concept_
applies, not when I need to worry about efficiency. So it doesn't matter to me
how it works, as long as it does what I expect.

------
jonjaques
OP could definitely have used more examples, but I think he's on the right
track. Where declarative or functional programming comes in really handy is
composition. Underscore has a lot of utilities that make it easy.

    
    
      var genericFilter = function(type, value) {
        return function(items) {
          return _.filter(items, function(i) {
            return i[type] === value;
          });
        }
      };
    
      var sizeFilter = genericFilter('size', selectedSize);
      var brandFilter = genericFilter('brand', selectedBrand);
    
      var appliedFilters = _.compose(sizeFilter, brandFilter);
    
      var filteredItems = appliedFilters(items);
      // which ends up doing  sizeFilter(brandFilter(items));
    

// edit for sloppy code;

------
simonv3
I work with a bunch of UX designers, and as the only developer here I'm often
confronted with their question of "why can't I just describe what I want
done?"

Their apprehension of tackling code is one I don't immediately understand, but
I do get that they don't want to think about the how, rather the what. It's a
funny parallel.

Here's a great video by Bret Victor who saw this problem, and tried to fix it
for animation:

<https://vimeo.com/36579366#t=1748>

------
iambot
I prefer the imperative style personally, I like things done the way _I_ want
... I kid, great write-up though.

------
ExpiredLink
What is the result of procedural programming? Functions that can be used
_declaratively_! The purpose of procedural programming is to encapsulate and
consequently eliminate "telling the 'machine' how to do something".

PS: What happened to 3GL vs. 4GL?

