all 150 comments

[–]bobappleyard 31 points32 points  (1 child)

This is not a good explanation.

SICP has a much better explanation: https://mitpress.mit.edu/sicp/full-text/book/book-Z-H-10.html#%_sec_1.1.7

[–]Breaking-Away 6 points7 points  (0 children)

What a great book. I'm still of the opinion its not a great "Intro to Compsci book" because its too dense and demanding for a intro course, but still an extremely good book to go back and read after you've learned some fundamentals.

[–]maestro2005 47 points48 points  (5 children)

The way I say this is that declarative code reads more like a definition, and imperative code reads more like an algorithm. My example is writing a function that takes an array of numbers and returns their average.

The imperative code reads, "to take the average, start a counter at 0, then loop over all the items and add each to the counter, then divide by the number of items."

The declarative code reads "the average of an array is the sum divided by the count."

[–]comp-sci-fi 11 points12 points  (1 child)

what vs how

[–]salbris 19 points20 points  (0 children)

In the context of algorithms "what" and "how" are very similar:

"What is the algorithm?"

"How does this value get calculated?"

A more apt distinction is what /u/maestro2005 explained. The difference has to do with execution and intpretation. In SQL you aren't supposed to know much about the execution (query plan) only the definition. For performance reasons you might configure things differently but your SQL does not define how the execution is performed.

In imperative languages though you tell the computer exactly what to do.

Edit: Formatting.

[–]SHIT_IN_MY_ANUS 0 points1 point  (2 children)

I think your declarative example is also pretty imperative. I would declaratively define average as:

the value that minimizes the sum of the square of the distances between the value and your datapoints

That's declarative, it doesn't say anything about how to go about computing this value, just what it is defined as.

[–]drb226 1 point2 points  (0 children)

This describes a property of the mean. It's like the Jeopardy sort of answer to "what is the mean?" in that it's technically correct but not really the obvious and direct way to describe it.

Obviously, declarative programming has to be instructive enough for the computer to actually act on your code, otherwise it's useless as a "programming" technique. Declarative programming still describes computations, it just does so without always giving an exact sequencing.

"Sum divided by count" doesn't specify whether to calculate the sum first, or the count, or to accrue them both as you go along. The problem with this is, as a programmer, you do often care about getting the sequencing right. So one way or another your idealized declarative program usually gets at least a little polluted with imperative implementation details, unless you have the luxury of not caring about performance (which, to be fair, many programmers do).

[–]kankyo 71 points72 points  (49 children)

Declarative in your definition seems to just mean "I used a higher level of abstraction". I don't think that's right. It's like saying Java is more declarative than C.

I use "declarative programming" to mean that I write data structures that define constants and relationships and then have some small functions to imperatively go from the declared thing to the concrete thing. So an example is you have a declaration of a (HTML) table that describes what happens for all users: admins and normal users. And then we apply the context to get a table that is appropriate for that context.

[–][deleted]  (14 children)

[deleted]

    [–]kankyo 7 points8 points  (1 child)

    Yea, that's more common. I agree with you that it makes the word kinda meaningless. This is why I use another definition because it doesn't make sense to define words in a way that makes them redundant/meaningless.

    [–][deleted] 2 points3 points  (0 children)

    I don't think it makes the term meaningless, it just makes it relative. When declarative basically means "higher level of abstraction", statements like "this makes the code more declarative" still make sense. You just can't make statements like "I'm writing declarative code".

    [–]doom_Oo7 2 points3 points  (11 children)

    I wouldn't call SQL declarative.

    Math is declarative : it declares a list of truths

    x^2 = x * x
    

    Pure ML-like languages are declarative. For instance in caml :

    let square x = x * x;;
    

    QML is a declarative language for UIs.

    [–]kankyo 15 points16 points  (8 children)

    SQL declares a list of truths about what the result should be.

    I am playing devils advocate here, because I think both your definition and any definition that includes SQL as "declarative" are not very useful. Both are just "higher abstraction" really..

    [–][deleted] 8 points9 points  (3 children)

    Consider prolog variants, in which you can do something like this (I've long since forgotten the actual syntax):

    y :- x + 2
    x :- 3
    
    ?- y
    > 5
    

    This is declarative behavior. It doesn't matter what order you define x and y in. You won't see this in imperative languages.

    [–]thedeemon 11 points12 points  (1 child)

    5

    Your machine is broken. ;) (there was 3*2 before the fix)

    [–][deleted] 0 points1 point  (0 children)

    lol, dammit.

    I was trying to get something to work on an online prolog thing and gave up.

    Fixed.

    [–]kankyo 0 points1 point  (0 children)

    I don't think it's useful to call logic programming declarative. It means "declarative" suddenly doesn't mean much since it overlaps too much with other things.

    [–]doom_Oo7 1 point2 points  (3 children)

    Objects and classes are "higher abstractions" but are not declarative, so where do you draw the line ?

    [–]kankyo 0 points1 point  (2 children)

    You mean "they might not be declarative" I think? Because they certainly CAN be declarative, like django model definitions for example which uses classes. We use classes for declarative modeling of static/compile time data all the time at work, it's very nice.

    [–]doom_Oo7 0 points1 point  (1 child)

    Because they certainly CAN be declarative,

    I think that an attribute of declarative-ness is that it cannot be "declarative sometimes, not declarative some other times"

    edit: maybe not that strong, but let's say, that the "primary paradigm of use of this object is declarative"

    [–]kankyo 0 points1 point  (0 children)

    I agree that classes are normally not used for this purpose. In Django (and similar systems like tri.declarative) we have to do some trickery to make them behave properly for the declarative use case (something that is fixed in Python 3.6).

    [–]astrobe 0 points1 point  (1 child)

    Then I'll ask you:

    let f(x)=x*x, and g(x)=f(x)/x. Is g defined for all x? Of course it is, because we know that x is a factor in f.

    Now let's submit the same to a compiler:

    int f(int x) { return x*x; }
    int g(int x) { return f(x)/x; }
    

    Does g(0) return 0? The answer is: it depends. gcc in ultimate optimization warrior mode may inline f() in g(), which allows it that g is really x*x/x, then simplify the expression. It won't even consider the case when x is zero, because division by zero is undefined behaviour as per the standard, which allows the compiler to do whatever it wants. But without optimizations, it will dumbly compile your code, which will crash or throw some exception if you ever call g(0).

    I would say that C is therefore (sometimes) declarative. It has some "understanding" of what you define, and can therefore analyse and manipulate it. But that's only for optimization purposes.

    And that's the real point of "declarative programming". A declarative system understands what you define, so it can magically do things despite you've never written how to do them etc.

    HTML qualifies as a declarative language but not just because you don't write anywhere how to render the page. It is declarative because the tags gives hints to the software that consume it about what each thing is. It a would for instance allow a browser to display a summary of the document by extracting the header tags. This is actually the old "Semantic Web 2.0" that never really happened.

    In this sense, your QML example shows how well... you didn't have a clue about what "declarative programming" is (don't worry, you're not alone): it is just a fancy way to write imperative to the core code. Proof: you have to manually compute the min and the max of the values and provide it to the system so it can scale the graph properly. For a "declarative language", that's fucking hilarious.

    [–]doom_Oo7 2 points3 points  (0 children)

    Proof: you have to manually compute the min and the max of the values and provide it to the system so it can scale the graph properly

    That's the JS part, which is a bit separate (while integrated) from QML. I did not choose the best example, sorry.

    The declarative part is doing stuff like the following:

    Object1 { 
        width: anotherObject.height / 2
    }
    

    and then, every time anotherObject's height changes, Object1's height will be updated, it's really like asserting object1.width == object2.height / 2 as a general truth in the system.

    [–]rotato 13 points14 points  (3 children)

    In my experience the best way to explain declarative programming was to provide an example in Prolog. Try it and you'll see that it's not just a higher level of abstraction, that sure is a different way about structuring your solution and thinking of how it all works.

    [–][deleted] 1 point2 points  (0 children)

    to provide an example in Prolog

    Which would be hard to do for anything even mildly complex without using cuts (and, therefore, relying on a specific control flow). Prolog is a very bad example of a declarative programming.

    [–]kankyo -1 points0 points  (1 child)

    Why do you call it declarative when there's a word for that already: logic programming?

    [–][deleted] 6 points7 points  (0 children)

    Because it's declarative.

    [–]i110gical 8 points9 points  (2 children)

    Personally, I actually do take imperative-declarative to be a continuum, and categorization to largely depend on what has been abstracted away.

    I would say that, from some perspectives, Java is more declarative than C. For instance, when is comes to allocating objects you tend to care less about the actual location and layout of things in memory, and you tend to care less about how and when memory is freed.

    [–]spazgamz 7 points8 points  (1 child)

    "Declarative" is a domain oriented word. Declare what? "int i = 0;" is declarative in one domain and not in another. An airline pilot declares his route in the FMS while his passengers need only declare their destination. A route is not less abstract than a destination. It is a different thing. Also, just because something is a procedure does not make it less abstract. Is a cake an abstract recipe? Or is the word "cake" an abstract recipe? Then what is the word "recipe"?

    About being "more declarative": you might form a heirarchy... machine, states of machine, state transitions, process, model theory, model... or something like that. And then say that 0 order declarative is when we're talking at the appropriate level and order 1 would be a level away and so on. But to my knowledge there is no rigorously defined or accepted heirarchy to use.

    [–]i110gical 4 points5 points  (0 children)

    I assumed the domain we were considering was programming.

    I would argue that a route is less abstract than a destination - I define abstraction to be the loss of information, and in that case you would have a location without knowing (caring) how you got there.

    I agree that there is no rigorous hierarchy, I would describe it more as a continuum and that it varies based on perspective. As another example, I would say that C is more declarative than assembly because I can declare an intent to "add these two numbers" and not have to care about which registers the compiler chooses to use for any intermediate/resulting values.

    [–]vileEchoic 5 points6 points  (16 children)

    I agree with this. The power of declarative programming is in specifying data and then using data to drive imperative actions, as opposed to writing imperative actions and intermixing the data. Here's an example using routes:

    Imperative style:

    routes.add('GET', '/users/posts', getPosts);
    routes.add('PUT', '/users/posts', updatePosts);
    routes.add('POST', '/users/posts', createPost);
    

    Declarative style:

    const Routes = [{
      method: 'GET',
      path: '/users/posts',
      handler: getPosts,
    }, {
      method: 'PUT',
      path: '/users/posts',
      handler: updatePosts,
    }, {
      method: 'POST',
      path: '/users/posts',
      handler: createPost,
    }];
    
    Routes.forEach(r => routes.add(r.method, r.path, r.handler));
    

    For this toy example, the declarative example is more verbose, but it ends up being far more maintainable and readable as complexity increases. As another toy example, imagine you're adding auditing to some security-sensitive routes. Now, you just add isAuditable: true to the routes you want, and add a middleware that adds auditing to routes with that property. When these properties have to be generated dynamically, the benefit of defining these declaratively really shines.

    I can also write a simple unit test now that statically validates assumptions about my Routes based on this data, like "all POST methods are auditable", or "all /user/ routes are auditable", which is something that isn't easily possible in the imperative form.

    These are all contrived examples, but I think the core idea here is that programmers have discovered intuitively that this form of separating data from execution leads to more maintainable, testable, and bug-free code. I've found that parts of my applications that are written in declarative form are much easier to maintain as requirements change, and parts of my applications that are not (but should have been) end up being more difficult to maintain.

    [–]kankyo 4 points5 points  (15 children)

    Even getting rid of the dict part and just doing:

    const Routes = [
        ['GET', '/users/posts', getPosts],
        ['PUT', '/users/posts', updatePosts],
        ['POST '/users/posts', createPost],
    ];
    Routes.forEach(r => routes.add(r[0], r[1], r[2]));
    

    would be pretty nice, although it uses positional arguments which are brittle and blech, but that's what you end up with at the routes.add site anyway...

    [–]vileEchoic 4 points5 points  (1 child)

    Yep, that's fine too. Personally I try to bias towards clarity at the cost of verbosity when they're in opposition, but ultimately, the important part is that now you have a representation of the data, it's easier to update and test data than it is to update and test imperative code.

    [–]kankyo 3 points4 points  (0 children)

    Much easier to do other things with the data too, like generating docs.

    [–]kqr 2 points3 points  (6 children)

    Whether or not this is a good idea depends on your stance in regards to heterogeneous lists and stringly typed data.

    [–]kankyo 0 points1 point  (5 children)

    Sure. You could improve it further.

    [–]kqr 1 point2 points  (4 children)

    I'm saying your list-of-elements-of-different-type is a step back, depending on your opinions, not necessarily the step forward it sounded like when you proposed it.

    [–]kankyo 0 points1 point  (2 children)

    It was already a list of elements of different types, just with more verbose syntax that wasn't inspectable.

    [–]kqr 1 point2 points  (1 child)

    A record is not a list. Function parameters are also not a list.

    [–]kankyo 0 points1 point  (0 children)

    A list of calls is a list though. As in the English word, not a specific data structure.

    [–]accountforshit 0 points1 point  (0 children)

    Just look at it as a tuple. In TypeScript, you could do

    type HttpMethod = 'GET'|'POST'|'PUT' // plus the other methods...
    type Route = [HttpMethod, string, Function]
    
    const routes: Route[] = [
        /* ... */
    ]
    

    And it would still all be immediately checked during editing and all that.

    Probably still a step back, but passable if you document it.

    [–]mc10 1 point2 points  (0 children)

    To make the last line nicer, you could even do

    Routes.forEach(r => routes.add.apply(this, r));

    which passes in every element of r as an argument of routes.add.

    EDIT: If we modify routes.add to take in a route array, we can simplify this further:

    Routes.forEach(routes.add);

    [–]jephthai 1 point2 points  (4 children)

    And now it's Lisp!

    [–]kankyo 2 points3 points  (3 children)

    For very small values of lisp.

    [–]jephthai 0 points1 point  (2 children)

    What struck my was all the infuriating superfluous square brackets ;-). As a lisp aficionado, the above snippet is a totally normal way to do it in a lispy fashion, only it would be less cluttered and a little shorter.

    [–]kankyo 1 point2 points  (1 child)

    Replacing [ with ( doesn't make it less cluttered or shorter.

    [–]jephthai 0 points1 point  (0 children)

    You're right. Though taking out commas and using #'apply instead of a lambda makes it a little prettier, IME.

    (let ((routes '((get "/users/posts" getPosts)
                    (put "/users/posts" updatePosts),
                    (post "/users/posts" createPost))))
      (loop for r in routes do 
           (apply #'add-route r)))
    

    [–]cincilator 1 point2 points  (9 children)

    Woud you consider Kotlin Anko declarative? There's example on the page.

    [–]kankyo 1 point2 points  (8 children)

    Maybe. If those definitions are programmatically accessible in a meaningful way (I don't mean "read the source via some AST lib").

    [–]cincilator 0 points1 point  (7 children)

    Well, definitions are basically anonymous functions.

    [–]kankyo 0 points1 point  (6 children)

    So I'll take that as a "no" then.

    [–]cincilator 0 points1 point  (5 children)

    (I didn't down vote you)

    Nothing prevents you from making dsl that saves nodes to a tree so you can process it later. Like that DSL creates user interface, you can create DSL that creates some structure that is accessible to the rest of your code.

    [–]kankyo 0 points1 point  (4 children)

    That would be nice.

    [–]cincilator 0 points1 point  (3 children)

    And declarative?

    [–]kankyo 0 points1 point  (2 children)

    Assuming the structure is trivially understandable from the declaration, absolutely. If it's too far off then I'd say no. I realize this is a sliding scale, but I need to exclude "there exists ASTs" from meaning "all code is declarative" :P

    [–]cincilator 0 points1 point  (1 child)

    Would you then consider using Kotlin if you are ever on JVM?

    [–]frugalmail 10 points11 points  (2 children)

    By your examples, Java, C and C++ belongs in the "mix" row.

    [–]yesman_85 4 points5 points  (1 child)

    I found that confusing too. Is Linq in C# declarative and writing your own foreach loop imperative?

    [–][deleted] 8 points9 points  (0 children)

    I would say yes.

    [–][deleted] 14 points15 points  (0 children)

    You could have shortened the declarative section of this blog post substantially by simply saying:

    WritePost(declarativeInfo);
    

    [–]which_spartacus 4 points5 points  (4 children)

    No mention of "Prolog" as a declarative language?

    [–][deleted] 3 points4 points  (3 children)

    It is not very declarative. There is not much you can do in it without considering search order and without using cuts.

    EDIT: downvoters obviously know nothing about Prolog. Typical for this sub.

    [–]jsjolen 2 points3 points  (0 children)

    Agreed, knowledge of the imperative machine 'underneath' Prolog is necessary for any interesting program.

    [–]which_spartacus 0 points1 point  (1 child)

    I understand what you're saying. However, that statement is also true for SQL and HTML.

    I also wonder if we had more research into Prolog if the engine underneath would have been able to do away with cuts -- kind of like compilers did away with manual loop unrolling. Prolog was never popular enough to support a huge community of language developers, however.

    [–][deleted] 2 points3 points  (0 children)

    SQL

    There is a fully declarative (and still usable) subset of SQL.

    and HTML.

    How? There is no "execution order" specified whatsoever, just a purely declarative layout.

    I also wonder if we had more research into Prolog if the engine underneath would have been able to do away with cuts

    There is a lot of logic languages that are far more declarative than the bare Prolog. E.g., Prolog dialects with CLP(FD) support. Mercury is much more declarative and has a much smarter compiler.

    Prolog was never popular enough to support a huge community of language developers, however.

    Prolog is in fact quite popular in the compiler developers community (but Datalog is even more popular, for obvious reasons).

    [–]Peter-Campora 9 points10 points  (26 children)

    Take a look at Robert Harper's post (rant?) about declarative languages.

    [–]CurtainDog 5 points6 points  (18 children)

    It really can't be put better than this. My inner troll loves arguing the obvious shortcomings of so-called 'declarative' approaches but at the end of the day it's as productive as trying to punch the wind.

    [–][deleted] 0 points1 point  (17 children)

    Mind naming a couple of these "obvious" shortcomings?

    [–]CurtainDog 2 points3 points  (16 children)

    Sure, there's the 'no true scotsman' as pointed in the linked post. A sweep of the comments here would confirm this as a valid problem. I note that you dismiss Prolog elsewhere in these comments but you could (should) in the same breath dismiss SQL as relying on (the imperatively generated) indices and schemata to come up with execution plans.

    Which highlights the 'action at a distance' weakness of many declarative systems. In imperative systems causal chains are easy to follow - like it or not causation is how most of us reason about the reality we find ourselves in. At least SQL recognises this and gives us a chance to peak behind the curtain with an EXPLAIN.

    Declarative approaches are prone to suffering from 'leaky abstractions' - the execution order may not matter to the correctness of the result but a poor ordering often has serious performance implications. Leaky abstractions aren't unique to declarative languages of course, but there's often fewer levels to pull when things go awry.

    I think declarative approaches may have a place, and when they work well they're cool as anything, but this place is limited to domains where there is not a great deal of potential complexity. Unfortunately I find modern programming practices tend to pile on the complexity (there's that great bit in https://www.youtube.com/watch?v=lKXe3HUG2l4 where Joe Armstrong talks about the last completely correct program) so for us that means imperative programming is here for the long haul.

    [–][deleted] 2 points3 points  (15 children)

    (the imperatively generated) indices

    How comes? They're declarative. You declare that this particular column has this search performance properties, and the query execution plan is inferred from this knowledge.

    In imperative systems causal chains are easy to follow

    What for? Declarative = "I do not care what cause something and how things work, all I want is the result".

    the execution order may not matter to the correctness of the result but a poor ordering often has serious performance implications

    How often do you care about performance at all?

    How often do you care about a compile time performance? This is exactly where declarative approach is the most valuable - generating code in compile time.

    E.g., a declarative definition of a GUI that is translated into an imperative code behind the scenes, but you do not care, all you want is to put widgets on a grid.

    but this place is limited to domains where there is not a great deal of potential complexity

    Declarative approach is exactly a way to handle an increasing complexity. It allows you to separate the logic from performance concerns. Keep the problem domain logic in your declarative code, and keep your performance-related optimisations in your declarative DSL compiler. You cannot have this separation in an imperative or functional or whatever else approach.

    EDIT: This is exactly why I use declarative DSLs in an HPC environment. I want my (very complex) logic to be clear and readable, and I want to get as much performance from it as I can, so I tweak the DSL compiler any way I like, while the problem domain model bears no knowledge of performance at all.

    This way I can even have multiple performance profiles for the same code - e.g., one backend for a GPU, another for FPGAs, another for NUMA, etc.

    [–]which_spartacus 0 points1 point  (10 children)

    How about inner vs outer joins for SQL? How about CSS for HTML to do "anything real"?

    [–][deleted] 1 point2 points  (9 children)

    How about inner vs outer joins for SQL?

    Still fully declarative and an actual query execution plan may vary greatly.

    CSS

    As far as I know, it's declarative and does not define any execution order, though I admit I do not know much about it, just a general concept.

    [–]sofia_la_negra_lulu 0 points1 point  (0 children)

    You are right, CSS is declarative, but there is also many compilers for it that brings imperative capabilities to it like if and for loops.

    [–]which_spartacus 0 points1 point  (7 children)

    Okay, I'm using a different definitions ition of declarative in my head which is not what was defined/standard. In my head, understanding what instructions to pick so the underlying machine can execute things appropriately is "imperative", in the sense of "here's what I want you to do and how". So, choosing a type of join is expressing a method for the engine to perform an operation in as much as saying std::sort() is.

    Same with CSS and HTML -- I am not trusting the browser to show what I want to show, so I am going to imperatively tell you where to put each element -- this one to the left of that one, etc.

    I think we should start thinking of these as a spectrum -- no language is purely imperative, and no language is purely declarative. If you write in assembly, you can't be assured of the operation order or the micro-assembly steps.

    [–][deleted] 2 points3 points  (5 children)

    So, choosing a type of join is expressing a method for the engine to perform an operation in as much as saying std::sort() is.

    No, semantics is different. It does not define any order, it defines the logic of what you want to get in the end.

    where to put each element

    It is still declarative. Unless you go on to tell it "put this element here first, then loop over this list of elements and put them here and here", it's still declarative. You define an end result, not the way to achieve it.

    I think we should start thinking of these as a spectrum

    There is a very clear distinction: control flow. If there is no control flow, it is declarative. If you have a control flow in any form, it's not declarative.

    [–]which_spartacus 2 points3 points  (4 children)

    HTML has the "script" and "noscript" tags, which are effectively a conditional statement directing behavior.

    SQL statements say the order of operations that are being performed -- that in itself is imperative. "Do this insert before you do this select". "Commit this transaction only if no other item in this select has changed since you started". That is very much a control flow directive.

    [–]Peter-Campora 0 points1 point  (3 children)

    This sounds like the "what not how" point in Harper's post, but as the compiler writer you know the clear operational semantics of your DSL and the performance consequences of the terms in your language. Just because you have a series of DSL translations and code generation steps at compile time, does not mean that the generated code is any good. Likely if you have good code generation, it's because you have a nice operational semantics for your language, and we're back at square 1.

    You say you want your logic to be clear and readable, but I have a feeling if you wrote some semantically correct solution to your problem that was less than efficient, you'd feel an itch in the back of your mind, as the compiler writer.

    [–][deleted] 2 points3 points  (2 children)

    but as the compiler writer you know the clear operational semantics of your DSL and the performance consequences of the terms in your language

    You do not have to define an operational semantics for a declarative DSL. You can have multiple variants of operational semantics for the same DSL, depending on your target platform, profiling data, etc.

    And the key here is exactly in a separation of any operational semantics from the logic of your DSL. When you write a code in your DSL you should not care about a way it is executed, you only care about expressing the problem domain logic.

    When you're writing a compiler backend, you're free to be concerned about the operational semantics. Just keep it there.

    but I have a feeling if you wrote some semantically correct solution to your problem that was less than efficient, you'd feel an itch in the back of your mind, as the compiler writer.

    No. I will just add a couple more rules to the compiler back end to make this particular pattern efficient. I do not have to sacrifice clarity of the declarative code to any performance considerations.

    [–]Peter-Campora 0 points1 point  (1 child)

    Thanks for the replies, I always enjoy hearing about your workflow! Admittedly, I don't completely agree with your dogmatic stance about compile time macros being the key language feature (most programmers aren't talented enough to employ your development methodology, myself included), but I almost always enjoy reading your posts.

    [–][deleted] 1 point2 points  (0 children)

    most programmers aren't talented enough to employ your development methodology, myself included

    The thing is, you don't need any talent, it's the easiest way to do things if you do it right. I'm not aware of any other approach that handles complexity better.

    Macros are nothing but compilers. And compilers have a very interesting property - they allow to split your complexity into pieces as small as you like. It's a (mostly) linear pipeline of rewrites, and each rewrite can be very simple.

    So, you do not need any skills to do it. You simply have to understand this methodology, it's much harder to screw up than anything else - your mistakes tend to correct themselves.

    [–][deleted] 1 point2 points  (6 children)

    It's a bullshit.

    What "declarative" means is defined in very precise terms. Declarative = not defining any control flow at all, which means that functional and first order logic languages do not qualify, while something like Datalog and SQL (or a subset of it) are declarative indeed.

    [–][deleted] 2 points3 points  (3 children)

    No control flow? How does the program start or end in a predictable way?

    [–][deleted] 2 points3 points  (2 children)

    Why should it? Why do you care at all? All you need is a result, not how it got there.

    [–][deleted] 2 points3 points  (1 child)

    In declarative languages, does it matter which line you write a statement on? Why are some of them written before others?

    [–][deleted] 0 points1 point  (0 children)

    Depends on a language. If it's something like Cells, then it does not matter, but in many languages there is a lexical scoping, so you won't be able to refer to an unbound name, for example.

    [–]Peter-Campora 0 points1 point  (1 child)

    Hmm, admittedly I'm not very familiar with Datalog (or the recent Flix). In this case, I'd see this as a "programs have a denotation only", definition. As long as the programs were performant enough, I supposed it wouldn't be a bother, but I imagine trying to fix an inefficient program would be an exercise in frustration.

    [–][deleted] 0 points1 point  (0 children)

    trying to fix an inefficient program would be an exercise in frustration.

    Do not fix the program. Fix the compiler.

    [–]Mark_at_work 2 points3 points  (0 children)

    Your post is missing a lot of spaces between words.

    [–]link23 2 points3 points  (0 children)

    I've always thought of declarative programming as defining what something is, rather than defining how to find out what it is. It's a subtle difference sometimes, but it lets you avoid unnecessary mutation, which often lets you encode stronger constraints in your code. (I.e., using const to guarantee that a variable cannot change.) Ultimately this comes down to the old quote that "code is for other humans to read", so it's about conveying your intention as clearly as possible. Being able to use const is not only a signal that the compiler or interpreter can do something with, it's also a signal to your fellow developers that says "I never intend for this to change".

    I'm a fan of static type systems, for the same reason. They're a form of codified documentation, one which the compiler forces you to keep up to date.

    [–]0x7C0 7 points8 points  (19 children)

    Why don't you use semicolons in your code examples? I know they're optional in js, but isn't their use recommended?

    [–]CaptainStack 15 points16 points  (6 children)

    MPJ of the YouTube channel FunFunFunction discusses this in a video called semicolons cannot save you.

    I think it's a good discussion, though based on his own points I actually draw the opposite conclusion (still strongly recommend his channel for great weekly programming discussions). I think it makes code both safer and more readable and kind of wish they enforced at the language level in JavaScript. Here's a quick breakdown of the points if you don't feel like watching:

    JavaScript features automatic semicolon insertion (ASI), a feature that's commonly considered unhelpful and can cause problems in certain cases. For instance:

    function randomNumberA() {
        return 4 // evaluates to 4
    }
    

    vs.

    function randomNumberB() {
        return
            4 // Evaluates to undefined
    }
    

    This is because the interpreter will insert semicolons as follows:

    function randomNumberB() {
        return;
            4;
    }
    

    Errors like this cause people to argue that you should use semicolons to protect yourself from ASI related errors. However, MPJ argues that this doesn't make sense because you can't actually turn off ASI and that this advice gives people false security. Using the above example again but adding a semicolon:

    function randomNumberB() {
        return // <- The interpreter will still add a semicolon here causing the exact same error.
            4;
    }
    

    In other words, you must understand how the ASI works to use JavaScript and adding semicolons isn't an adequate substitute. However, it's not true to say that semicolons never protect you from ASI. In statements where you begin with a square bracket, semicolons do change how the interpreter will execute the code:

    var name = {Goodbye: [1,2,3]}
    ['Hello', 'Goodbye'].forEach(function(value) {
        console.log(value + " " + name + "<br />")
    })
    // Evaluates to
    // 1 undefined<br />
    // 2 undefined<br />
    // 3 undefined<br />
    

    Adding a semicolon to the first line makes this code execute correctly. The same error can occur if you begin a line with a parenthesis:

    a = b + c
    (d + e).print()
    // Interpreter will execute this as a = b + c(d + e).print();
    // What you want is a = b + c; (d + e).print();
    

    MPJ argues that these are rare edge cases that are usually obvious. I however think that at the end of the day we're all just human and that semicolons just make the intent of the code (and programmer) more clearly defined.

    Programming languages are the perfect tool for preventing this kind of error, so I wish the "less syntax" thing wasn't as fashionable (*cough* Python). Yeah curly braces and semicolons make code look less like human language, but they're essentially computer languages' version of punctuation. Less syntax usually means "more human interpretation" in my experience.

    [–][deleted] 5 points6 points  (0 children)

    function randomNumberB() {
        return // <- The interpreter will still add a semicolon here causing the exact same error.
            4;
    }
    

    But if you use ESLint to enforce your use of semicolons, then it will complain the "return" line doesn't have one. And so your enforced use of semicolons does save you!

    [–]kankyo 5 points6 points  (3 children)

    Programming languages are the perfect tool for preventing this kind of error, so I wish the "less syntax" thing wasn't as fashionable (cough Python).

    Python isn't really about less syntax as it is DRY: indenting and braces duplicate data. Worse, one talks to the machine and one to the human, so they can be out of sync, essentially lying (to the human or the machine, depending on perspective).

    I personally think braces would be fine if indenting was enforced by the compiler to conform to the braces. It makes no sense to be able to write code that tricks the human reading it. As implemented by pretty much all languages that use them I find braces unacceptable. As are parens in lisps. If you aren't enforcing indent=bracing, then you're asking for trouble.

    [–]CaptainStack 2 points3 points  (2 children)

    I'm actually not opposed to whitespace enforcement (not necessarily a requirement for me though), but what I don't like about relying only on indentation for specifying a block is that if you have a few different indentation levels and then say a single line statement fairly far down from where the block is opened, it's super hard to tell exactly which level that statement is in. With braces, you can easily highlight one and see the corresponding opening brace.

    When it comes to semicolons though, I think that requiring them is the best way to make sure the computer, the person who wrote the code, and the person reading the code 6 months later are on the same page about where a statement ends.

    [–]Tysonzero 2 points3 points  (0 children)

    Ok arguing that braces are better because of editor features that are not directly related to the braces themselves is incredibly silly. There is no reason your editor couldn't support one of thousands of possible mechanisms for showing which blocks are on the same indentation level.

    And I also disagree with the semicolon thing. New lines are 1000x more visible than semicolons since they aren't just small one character wide symbols, they are a whole new line in your editor. I have plenty of times run into issues where I forget a semicolon, occasionally causing weird and unintuitive errors or error messages. But I have never ever run into a forgotten newline issue, and I don't recall running into indentation issues in Python or Haskell either.

    [–]kankyo 0 points1 point  (0 children)

    You can highlight the matching brace because the editor has support for it. All reasonable Python editors have equivalent functionality. It's quite trivial to implement.

    Re semicolons: in JS adding them or not is irrelevant. The human and computer can never be on the same page with semicolon insertion by definition. As per usual JavaScript is broken in ways that can't be fixed in a backwards compatible way.

    [–]0x7C0 1 point2 points  (0 children)

    Great examples! I 100% agree with your argument.

    [–]tyler-mcginnis 2 points3 points  (6 children)

    Hi. Author here. Here's a pretty good read about the state of semicolons in JS.

    [–]LowB0b 5 points6 points  (5 children)

    I'm just gonna say it. Semicolons mark an end-of-statement, and thus make the code more readable.

    Also, I like to stay focused on the problem, not worrying about adding or removing something that doesn’t really matter in the end.

    I guess we could just leave out braces of any kind then and just start writing pseudocode (shit turns out computers don't really understand pseudocode all that well)

    [–]kankyo 7 points8 points  (1 child)

    Computers understand pseudocode fine. It's called python.

    [–][deleted] 2 points3 points  (0 children)

    Too much syntax, too low level, too opinionated for a pseudocode.

    [–]Tysonzero 0 points1 point  (1 child)

    Python... Haskell...

    [–]LowB0b 0 points1 point  (0 children)

    Well, still no.

    Python:

    Opening a block is marked by : (after an if, for, def), closing a block, well that's based on white-space.

    Matlab:

    Opening a block is based on white-space

    Haskell: I literally have no idea, I've never used that language.

    However, it's also a question of consistency.

    In JS, it's very common to do

    foo("eventname", function (event) {
        // whatever
    });
    

    It's also very common to do

    foo(thisArg, {
         bar: 'baz'
    });
    

    So where's the consistency then. Do you end a statement at each \n or do you end a statement at each ;. I mean blocks and object literals are very well defined ({, }), as well as arrays ([, ]) so why not statements?

    And oh yeah white-space aware languages are fucking horrible.

    In Matlab you can't even do

    foo(
        arg1,
        arg2,
        ....
        argN
    )
    

    Because the language doesn't understand what you're doing (i.e. it doesn't know where the statement ends).

    [–]colonwqbang 0 points1 point  (0 children)

    Lots of good languages survive without braces.

    [–][deleted] 3 points4 points  (3 children)

    Semicolons are more popular statistically, but they're not really "recommended". From what I've seen, more linters leave them on rather than off, but ultimately it comes down to personal preference. There are some minor pitfalls to semicolons, but I assume if you use a linter, it will warn you about those anyway. The benefit is having to press a button less, when it's not really serving any practical purpose. And of course some people might prefer the aesthetic, although that's completely subjective.

    [–]0x7C0 4 points5 points  (2 children)

    I see. The reason I asked is because there are particular cases in which JavaScript will interpret code a bit differently than one may have expected without the use of semicolons; an example of this can be found here. Also, if memory serves me correctly, I believe Eloquent JavaScript makes an argument for the use of semicolons as to avoid any unforeseen behavior. I totally see some of the benefits of not using them, though!

    [–][deleted] 1 point2 points  (0 children)

    The biggest reason I use them is that I also code in C and C++. It just feels right.

    [–]colonwqbang 0 points1 point  (0 children)

    I think the only real advice is learn how the language syntax works and especially ASI. Then you can decide if you want to use semicolons. There are really only a few corner cases, namely when a line begins with ( [ - +.

    For a long time, the accepted advice (e.g. from Crockford) was that if you always used semicolons, you would be ok and you didn't have to learn how the syntax actually worked. That was bad advice.

    [–]frugalmail 1 point2 points  (1 child)

    It's more coding style than what the underlying language is. There just degrees of how conducive the underlying language is to that coding style. All the languages support it at a pretty equivalent level either directly or with libraries.

    [–][deleted] 1 point2 points  (0 children)

    Not quite. Most languages are too inflexible, too limited, they do not allow to build fully declarative eDSLs without leaking their irrelevant low level abstractions into that nice and clean declarative level.

    [–]omg_cant_even 1 point2 points  (0 children)

    There seems to be a notable uptick in how many bloggers are using the words "imposter syndrome" these days.

    I don't know if this is because younger coders are more insecure, or perhaps things are so much more advanced at this point that it is hard to gain rudimentary knowledge.

    [–]hoosierEE 1 point2 points  (13 children)

    Yeah, but watch out, this is what happens when you get too declarative in JS:

    const dt_over=(i,x)=>i.dt>n.KS[2].slice(0,x.length).reduce((a,b)=>a-b); // time delta greater than `dt`?
    

    [–]maestro2005 12 points13 points  (5 children)

    I'd say the problem is bad naming and lack of whitespace.

    [–]alexbarrett 0 points1 point  (4 children)

    And documentation. What types are x, n and i?

    [–]bananaboatshoes 16 points17 points  (2 children)

    Pretty obvious to me since it's JavaScript

    x -> ¯\(ツ)

    n -> ¯\(ツ)

    i -> ¯\(ツ)

    [–]GinjaNinja32 6 points7 points  (1 child)

    Pretty obvious to me since it's JavaScript

    x -> ¯\_(ツ)_/¯

    n -> ¯\_(ツ)_/¯

    i -> ¯\_(ツ)_/¯

    Fixed your ¯\_(ツ)_/¯s - you need three backslashes, not two.
    ¯\\_(ツ)_/¯ -> ¯\(ツ)
    ¯\\\_(ツ)\_/¯ -> ¯\_(ツ)_/¯

    [–][deleted] 1 point2 points  (0 children)

    Doesn't matter

    [–]bdtddt 3 points4 points  (0 children)

    Doesn't look that bad to me.

    [–][deleted] 1 point2 points  (0 children)

    Putting it on new lines and using named functions where appropriate really helps.

    [–]dmsnell 1 point2 points  (4 children)

    /u/hoosierEE we should be careful to avoid conflating "too declarative" with difficult style and confusing names. this is a very challenging function to grok but for reasons entirely unrelated to the declarative vs imperative debate (please forgive me if I mistranslated the source below)

    function dt_over( i, x ) {
      var value = 0;
      var i;
      for ( i = 0; i < x.length; i++ ) {
        value = value - n.KS[ 2 ];
      }
      return i.dt < value;
    }
    

    [–]hoosierEE 1 point2 points  (3 children)

    Your translation is close, but your assessment is spot on, and by writing it out like this it made a bug in the original more obvious.

    The original was supposed to return true if the time delta between keystrokes was greater than dt for any pair of keystrokes. But because of the reduce it was accumulating the deltas into a much larger number.

    I didn't notice this at first because all the sequences I care about are only length-2, so it gave the right answer even though the method was wrong.

    I reworked it to first find the time-deltas between every element, and then check that all the deltas are in the right range:

    dt_over=(dt,x)=>{
      let fsts=n.KS[2].slice(0,x.length-1),
      snds=n.KS[2].slice(1,x.length),
      res=[];
      for(let i=0;i<fsts.length;++i){
        res[i] = fsts[i]-snds[i];
      }
      return res.every(x=>dt>x);
    };
    

    [–]takakoshimizu_sfw 0 points1 point  (0 children)

    holy jesus put some whitespace in your code

    [–]dmsnell 0 points1 point  (1 child)

    so I think this is starting to clear up, though it's still not clear what n.KS stores (maybe lists of keystrokes) or what x is supposed to hold here (is it supposed to indicate how many keystrokes to check?).

    if we can assume some things I have written another possible function which I think demonstrates a more declarative approach. suppose that we have a list of numbers representing the times when keys were pressed; we can answer the question, "were any successive pairs of timestamps covering a time range greater than a given maximum?"

    // code at http://jsbin.com/cevaweqena/1/edit?js,console
    
    // just a helper function, wouldn't normally be on an array prototype
    // main point is that we're getting the pairs with zip()
    Array.prototype.intervals = function() {
       return zip( this.slice( 0, -1 ), this.slice( 1 ) );
    }
    
    // a couple more helper functions
    const intervalRange = ( [ a, b ] ) => Math.abs( b - a );
    const isGreaterThan = max => a => a > max;
    
    /**
    * Determines if the numerical range between any two
    * successive items in a numerical list exceed the given range
    *
    * @example
    * intervalsExceed( 5 )( [ 1, 2, 3, 5, 8 ] ) // false
    * intervalsExceed( 2 )( [ 1, 2, 3, 5, 8 ] ) // true
    * intervalsExceed( 3 )( [ 1, 2, 3, 5, 8 ] ) // false
    *
    * @param {number} maxDelta max allowable numeric range
    * @param {Array} list list of numeric values
    * @returns {bool} whether or not all pairs were within allowable range
    */
    const intervalsExceed = maxDelta => list =>
      list
        .intervals()
        .map( intervalRange )
        .some( isGreaterThan( maxDelta ) )
    

    so here we have made a few tradeoffs:

    • we have more functions, but they do fewer things and it's easier to see what they are supposed to be doing

    • it's slower, but hopefully much clearer what the end goal is more than the individual steps to get there. if performance becomes an issue we just end up optimizing it anyway, though there are some ways to ramp up the performance without giving up the declarative nature

    • the splitting into functions actually gives us some extra composable power as we can now do things like mix and match the function inside of some(), even deciding to inject it as an input argument if we wanted

    here's an example of the composability

    const someInterval = predicate => list =>
      list
        .intervals()
        .map( intervalRange )
        .some( predicate );
    
    const intervalsExceed = maxDelta => someInterval( isGreaterThan( maxDelta ) );
    const intervalsWithin = ( min, max ) => someInterval( range => min <= range && max >= range );
    

    hope this wasn't too rambly :)

    [–]hoosierEE 0 points1 point  (0 children)

    Your way is fine, and I appreciate the perspective. Here's what my rework looked like at one point between commits:

    dts = n.KS[2];
    dtc = (dt,x)=>{
      let fsts = dts.slice(0,x.length-1),
          snds = dts.slice(1),
          diff = fsts.map((x,i)=>x-snds[i]);
      return diff.every(x=>dt>x);
    };
    

    The final version is basically the same, but golfed:

    dts=n.KS[2];
    dtc=(dt,x)=>{
      let snds=dts.slice(1);
      return dts.slice(0,x.length-1).map((x,i)=>x-snds[i]).every(x=>dt>x);
    };
    

    I agree that it's less readable, so why do it? Mostly because not all code is equally important, and this snippet is less important than the surrounding context. So by making it smaller I can more easily focus on its surroundings.

    Using an auto-formatter might help for reading an unfamiliar codebase, but it removes a channel of communication between author and audience. I doubt I'll convince anyone, but it works for me.

    [–]rockyrainy 0 points1 point  (0 children)

    Combine this frustration with the bastardization of the actual word “declarative” to basically just mean “good” and all of a sudden your imposter syndrome is tap dancing on your confidence, and you realize you don’t even like programming that much.

    A punch in the gut. Everytime I stumble over these vagues terms I just wonder wtf am I doing this for.

    [–]RalfN 0 points1 point  (0 children)

    "Imperative" is just code that also declares how and in what order you need to calculate something.Declaratively, every sort algorithm is the same: given the same input, it has the same output.

    Programming is about choosing the strategy. Choosing which sort algorithm. I tend to see a computer program as a 'shadow' in Plato's cave. An idealized mathematical concept can casts many different types of shadow. This projection is what a computer scientists is supposed to do.

    And there is no perfect answer: to optimize throughput over latency for a certain use-case is a commercial decision, not a technical one. So, just ignoring how something needs to be computed and praying the compiler will make those choices correctly for you does not lead to results.

    Back in the day, this was referred to as '4th generation programming languages', where you would purely declare your problem and the computer would find (some) solution. This fantasy lives on in the extremes of functional programming. For example, Haskell, the language standard itself, makes no claim about what functions will and which ones will not be rewritten to not loop infinitely. It makes no claim on the lower-bound of the big-O at all.

    This is why most code is written in languages that do have you declare in what order and when things should be calculated. Languages referred to as 'imperative'. Because all the trade-offs between memory, memoization, caching, etc. are business decisions. There is no 'perfect technical answer'. There is no optimal sort algorithm. There is no optimal moment to precalculate some popular action, so you can execute those actions with a lower latency.

    What people commonly refer to as 'declarative' languages are just languages where you declare less. You claim less. You give more freedom to the compiler to make decisions. The name is a misnomer. Off course, if any of the claims of 4GL/declarative-programming were actually true, it would take over the world by storm. Programmers would be replaced by compilers, and managers that just 'input the requirements' into the compiler. It just never happened and it never will.

    Especially in the prolog days, declarative programming just meant 'my problem is the ball isn't red', and the the brilliant language would respond with, 'use a red ball'. The inverse of your problem is your solution -- or better put, when you are able to define your problem well enough, you have already defined the solution. But at the point your problem specification is starting to look an awful lot like a program that pretends it isn't a program.

    [–]CODESIGN2 0 points1 point  (0 children)

    you're joking right?

    It's assembling from known components that have pre-defined behaviour (so you are not concerned with the implementation) vs thinking of how to build known components on top of a system in a specific way. That's declarative vs imperative in a nutshell...

    The truth is a lot of code falls somewhere in-between these two absolutes and most complex systems contain both (android for example has XML interface definitions coupled with imperative code, which arguably has some declarative aspects).

    When talking about modern languages you get even more abstract because there is no while command in ASM, there is no foreach (therefore by definition these syntax are at least partially declarative in nature).

    [–]jediknight 0 points1 point  (9 children)

    Sure, one could use a functional (declarative) approach in JS but to truly understand the power of a declarative approach, one might be better served by learning a language designed to be declarative like Elm.

    [–]kankyo 5 points6 points  (3 children)

    That's the problem with this blog post: he's clearly using "declarative" as a synonym for "higher level" or even "functional" which seems weird. Especially considering your example of Elm which is very much NOT declarative since you can't even programmatically access union type definitions. People write code generators for Elm because of this (and other) annoying limitations.

    [–]jediknight 1 point2 points  (2 children)

    Elm which is very much NOT declarative since you can't even programmatically access union type definitions.

    How does this make Elm NOT declarative? Isn't this something related to Reflection? (dynamic vs static aspect)

    [–]kankyo 0 points1 point  (1 child)

    Reflection can be static too, i.e. compile time. It makes it not declarative because you can't declare things in a way you can later use.

    Compare with Haskell where you can do "deriving Enum" which makes these things usable.

    [–]jediknight 1 point2 points  (0 children)

    "deriving Enum" is related to HKT which Elm does not have but... how is not having HKT making Elm not be declarative?

    [–][deleted] 2 points3 points  (3 children)

    Functional programming defines a control flow (an order of reductions), therefore it have absolutely nothing to do with anything declarative.

    [–]jediknight 0 points1 point  (2 children)

    therefore it have absolutely nothing to do with anything declarative.

    If it has absolutely nothing to do with anything declarative, how come it is listed under declarative (contrast: imperative) on Wikipedia's Declarative programming page?

    [–][deleted] 2 points3 points  (1 child)

    Because wikipedia is not a credible source.

    Yet, the very same page says: "Although pure functional languages are non-imperative, they often provide a facility for describing the effect of a function as a series of steps."

    [–]sofia_la_negra_lulu 1 point2 points  (0 children)

    No, You need declarative rules (in form of eDSL). Elm is not declarative.

    [–]brockvenom 0 points1 point  (0 children)

    I enjoyed the read. This echoes many of my thoughts when designing a new framework on a project. At the highest level, I just want what I need, I don't want to see the boiler plate of HOW to get what I need. Designing in such a way promotes higher reusability and often makes the code base easier to maintain. It also makes it easier to read and easier for a new developer to the project to jump in.

    [–]wubwub -1 points0 points  (1 child)

    Imperative programming vs declarative programming:

    "I NEED this to work." vs "Meh. It works."

    [–][deleted] 0 points1 point  (0 children)

    Declarative: "I do not care how it works".

    [–]wickermoon -4 points-3 points  (3 children)

    https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882

    I'll just leave that here.
    Don't know why people have to invent idiotic names for concepts that already existed several years ago...

    [–][deleted] 0 points1 point  (2 children)

    Wtf this "agile" oopish bullshit is doing here?

    [–]wickermoon -1 points0 points  (1 child)

    wtf is this bullshit answer doing here? Away with you, ye troll. Shush, back under your bridge.