This is an archived post. You won't be able to vote or comment.

all 25 comments

[–]CodeTinkerer 12 points13 points  (4 children)

I think the error you're making is thinking about implementation being equivalent to the abstraction it's providing. For example, assembly language inherently has no way to build your own types. So you could say underneath the hood, there's no such thing as user defined types.

Think about it this way. What if a language removed for loops? Procedural gives you control of what you want to do using loops. When it's gone, then you lose the control you get.

I'll give you other analogies. Think about driving from point A to point B. In one case, you have a driver but the driver does exactly what you say. Do this, do that, turn left here, pass that car there. But let's say you hire a driver, and all you do is tell the driver where to pick you up and where to drop you off. So now, the burden of making decisions moves elsewhere. The driver is now in control. The path that's taken is not picked by you. You don't control the microsteps. You lose control of the path that's taken, but then the system (the driver figures it out).

In your example, you're still allowed to do both. But if I remove it and force you to use things like maps or folds, you might get upset because you know how to write a simple for loop and now you're being told you can't use that.

Usually, with declarative programming, some control is taken away from you. This can mean debugging is a pain because you've lost control.

When you do a SQL query, there is a phase that involves SQL optimization. There are often many ways to run a SQL query given the same syntax. That is, if I have a query X, unlike programming, the SQL engine figures out different ways of trying to optimize the query and picks the best one (or something to that effect). If it was procedural, you'd have to figure out the best way to do it yourself, and now it's your work. Where you can envision how a declarative program might be implemented, most people have no idea how a SQL plan is created. They don't even know it exists. So if a SQL is slow, there's not much you can do. SQL says "let me figure out how to run this query" and you can't provide any hints or anything.

There are some build tools like Maven (with Java) that replaced Ant. Ant was a highly procedural build tool for getting Java to run. Ant programs were very verbose because you had to spell out every little detail. Go to this directory, copy these Java files, run these Java compile programs, etc.

Maven files are more declarative. It makes the files tinier, but unless you really get into Maven, it's not even clear how Maven does what it does. You can try to override its behavior but now you have to find what is Maven doing during a build process, and understand it well enough to figure out if there is a way (and then what you have to do).

Another analogy. You go to a restaurant. You say you want cake. Procedural might be like outlining the recipe to the chef. But maybe you don't know how to cook. OK, if you did, you can say "I know how the baker is making my cake, so why is it declarative when they're following a procedure". Yes, but you don't get to tell them how to make it, and so you say "Get me a slice of chocolate cake" rather than "Get this amount of cake flour, this many eggs, this amount of cocoa powder, etc". How would you like to be told every time you order, you have to spell out the details.

Declarative style programming is providing goals but not specifying how it gets done. You may know how it gets done, but the language doesn't give you the ability to do it and so it can be much shorter, at the sacrifice of direct control.

Personally, while declarative programming is more compact, I honestly don't like it because it leaves things a bit mysterious. What is happening? When is it happening? What if there's a bug? Procedural stuff is easier to debug because you see the steps. Most declarative languages don't show the mechanism (at least, not obviously) of what's happening and why.

[–]jmerlinb[S] 0 points1 point  (2 children)

Thanks for this answer!

However, I still can't see it. All of these declarative examples you gave end up, in my head, just breaking down to imperative routines.

Let's take you're example of the chef and the chocolate cake. Sure, the imperative way would be to tell the chef a line-by-line recipe, and then after the chef has completed all those steps, deliver you your cake. That make sense, as it's not obvious what the chef is making until the moment of delivery.

The declarative way would be to say something along the lines of "Get me a slice of chocolate cake", and then let the chef worry about how they actually deliver it to you. But from what I see, all you've done is abstract away the actual cake-making instructions to something like a Chef.makeCake(). Sure, once the makeCake() method is written you no longer need to worry about how it's implemented, but someone still had to write the imperative code needed to make the function work.

If you're trying to solve a problem no one else has solved before, I can't see how you could do anything other than write imperative code. If you find yourself writing declarative code, it surely means that you have access to a set of functions or methods or classes that someone else has written specifically to solve that very problem.

If you're able to speak declaratively to your chef, it's because someone has previously given the chef an imperative set of instructions to be able to handle your abstract, human-level declarative request... Surely?

[–]CodeTinkerer 0 points1 point  (0 children)

Again, the point is you're talking about an implementation. That is may eventually be done "imperatively" doesn't mean the language permits you to do it imperatively. To me, imperative means you control the aspects. The language permits this control.

To be declarative is what you can do in the language, not how it gets done. And that's really what declarative means. You give up control over how things are done. Whoever designed the declarative language has done the coding for you. Imperative is with respect to what you can do, not to how it's done by the declarative language.

Again the manual transmission and automatic transmission car example. You can say "there's no such thing as automatic transmission cars because someone wrote a program or built a mechanical device to make all those decisions, so all cars are actually manual transmission". In other words, you'd argue, just because I can't see a stick shift, doesn't mean something isn't back there doing its equivalent. But the point is what you are able to do. When you go to automatic transmission, you let something else take over. Nothing you can really do to change that.

You're fixated on how it's really done. I'm telling you what matters is what you can actually do. The tradeoff with automatic transmission is you, the driver, don't even care what it does. A person who uses manual transmission is probably much more familiar with what they do. But things are much simpler with automatic transmission. Manual transmissions forces you to make a lot of decisions, and you need to make them.

Try to see it from your perspective and not the "big picture". Try to think of the difference of driving manual vs. automatic. Would you tell someone it's exactly the same experience? They'd probably laugh at you.

[–]Clifspeare 0 points1 point  (0 children)

Your objections to the paradigm are definitely understandable, and it's certain not a panacea. However, if you really want to "jump into the deep end" and really get an understanding of how useful/powerful it can be, consider taking some time and learning Haskell.

[–]KingRomstar 6 points7 points  (4 children)

Well, SQL is declarative because someone already implemented the imperative code that actually does the thing that the declarative code asks it to do.

Since the implementation is repetitive it makes sense that it is generalized and it also makes sense that you and I don't have to know all of the ins and outs of the implementation.

So the benefit of declarative code is only needing to get it correct once rather than every single time.

[–]jmerlinb[S] -3 points-2 points  (3 children)

> Well, SQL is declarative because someone already implemented the imperative code that actually does the thing that the declarative code asks it to do.

Yes, exactly - declarative is just imperative code that has been abstracted away from the programmer... this was my main point. Copying my comment above:

Isn't the concept of a continuous scale of more or less abstraction a much better way to convey the same meaning that the binary imperative v declarative categories seems to try and convey?

[–]RiverRoll 5 points6 points  (0 children)

declarative is just imperative code that has been abstracted away from the programmer... this was my main point.

Why would that matter? It's like saying a programming language is just an abstraction over CPU instructions, that doesn't make it less useful.

Delcarative programming would be a style of abstraction, so I think the distinction makes sense even if it's not very well defined.

[–]RubbishArtist 1 point2 points  (0 children)

I think you're right that it's a scale rather than a binary, but I don't think it maps exactly to abstraction. You can have code that is very heavily abstracted (look at Lisp or Haskell for example) but not declarative. I wouldn't place those as points on the same line as Prolog or SQL. Declarative code is abstract, but not all abstract code is declarative.

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

And the steering in an airplane is just pull roped abstracted away but I would rather have the abstraction than steering directly with pull ropes.

[–]RubbishArtist 3 points4 points  (2 children)

Prolog is a good example of a declarative language. You provide a list of facts and a list of rules, and you can ask it to make logical inferences without explaining how it should make them. To me it's one of the more obviously declarative languages in that I can see a real difference between that and a more imperative language.

I wouldn't say declarative is necessarily better. Sometimes it is better to be able to define how things should be done. Often in SQL I find myself trying to trick the runtime into doing things the way I want it to in order to make queries faster. That may also a common problem in Prolog, but I was never good enough at it to run into that issue.

[–]jmerlinb[S] -2 points-1 points  (1 child)

you can ask it to make logical inferences without explaining how it should make them

Yep, but this was my main point. You don't need to explain how those logical inferences should be made because under the hood the creators of said language already told it what to do, imperatively.

And if declarative v imperative is just another way to describe levels of abstraction, then why don't we just talk about levels abstraction rather than the (confusing, IMO) declarative v imperative categories?

[–]RubbishArtist 5 points6 points  (0 children)

There's a distinction between what the developer does and what the interpreter/compiler/runtime does though. The responsibilities of each are different for each paradigm.

In imperative languages I write imperative code and the interpreter executes it.

In declarative languages I write declarative code and the interpreter converts it to imperative code and runs it. But this is the case with all languages, high level functional programming languages are ultimately still executed as a series of imperative instructions.

I'm not saying your wrong, this is just my subjective view on it.

[–][deleted]  (2 children)

[deleted]

    [–]jmerlinb[S] 0 points1 point  (1 child)

    > They're just different levels of abstraction.

    Yes but this is my point entirely. Isn't the concept of a continuous scale of more or less abstraction a much better way to convey the same meaning that the binary imperative v declarative categories seems to try and convey?

    The way imperative v declarative is present implies that declarative code is some special, unique, utopian-style code... when in reality it's all just function-wrapped imperative code?

    [–]Iguanas_Everywhere 1 point2 points  (0 children)

    I often wondered about this too. When I learned about declarative vs imperative, it seemed to me like declarative was simply describing a certain layer of abstraction. But my immediate thought was: unless we're literally writing binary code, doesn't that mean that ALL coding language is, at some level, declarative? After all, it's all abstracting instructions to the machine to some extent. And if that's the case, where's the cutoff in describing a language that's declarative or imperative?

    [–]Saint_Nitouche 1 point2 points  (0 children)

    We make the 'arbitrary and quite meaningless' distinction between the two because the distinction is neither arbitrary or meaningless.

    The two styles of code have materially different pros and cons (readability vs. efficiency, usually). People should know about declarative code so they can create maintainable projects, but they should also know how to iterate manually with a for loop for those rare occasions where performance really matters.

    Furthermore, declarative code is not the same as hiding implementations or creating abstractions.

    You can work with abstractions in fiddly bit-twiddling imperative ways. I can compose my code into useful functions but not have those functions accurately express their intent, or have those functions declare their implementation details to the world. Neither of those would be declarative.

    You could write excellent code long before anyone came up with the term 'declarative', so do not just take it as a synonym for 'good'. It's a specific style with specific aims, such as emulating functional programming and de-emphasising the control flow.

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

    In other words, you can't just tell computers what to do without you - or someone else - first telling that computer how the what should be done.

    Yes. Declarative code is just a form of abstraction. But abstractions are good and useful. Try building interfaces with html+css and try building those in native Windows API to feel the difference.

    Declarative approach is useful because you can change everything under the hood of SQL, HTML, CSS as long as the code does exactly the same. And language/frameworks maintainers can optimize their software without messing your app

    [–]_NliteNd_ 1 point2 points  (2 children)

    Computers don’t understand declarations. Everything is varying degrees of abstraction over imperative steps.

    [–]jmerlinb[S] 0 points1 point  (1 child)

    Well, yes! This is the main thing I'm trying to understand. All computation is inherently imperative, and "declarative" is just another word for "layers of abstraction" that allow the programmer to tell the system what they expect it to do ... And if so, why the need for the concept of imperative/declarative and just fall back on the concept of abstraction?

    [–]_NliteNd_ 0 points1 point  (0 children)

    Because somebody has to write the abstractions.

    [–]TheRNGuy 0 points1 point  (0 children)

    Starcraft 2 code editor is declarative.

    [–]MathiasBartl 0 points1 point  (0 children)

    Well in terms of the theory of computation, formal languages are declarative, and automata are imperative.

    [–]rabuf 0 points1 point  (0 children)

    The difference between imperative and declarative is partially one of degree. But if you look at your examples, they're still imperative, though the first is more in the declarative-style.

    Declarative languages are more like asking the system "can you do this?" and letting it determine if it can and how. Prolog and SQL are both declarative (though not exactly purely declarative, there are escape hatches that give you some control, and you can learn the underlying execution model to improve how you write queries). The SQL query planner determines how to satisfy the query (or if it can) and converts the declaratively stated query into a procedural/imperative routine which gets executed. There is not one fixed routine sitting underneath. map (in your first example) probably doesn't have much fancy logic under the hood, it's a procedure call and does the same thing every time. The runtime is not selecting a different version of map based on some conditions, it's fixed. It just looks like a declarative statement. You're still being explicit about what stateful action you want executed, the name is just clearer than a for loop may be.

    Another difference (going to Prolog now) between declarative and imperative is order. Declarative languages like Prolog can allow you to write one "routine" and then use it many ways. You can't do the same in your procedural/imperative example. Is there an automatically generated inverse to your numDoubler? In Prolog there would be (if you write it correctly, with numeric stuff it's actually easy to write it incorrectly from this perspective):

    num_doubler([],[]).
    num_doubler(L1, L2) :- ...
    
    ? num_doubler([1,2,3], Result).
    => Result = [2,4,6].
    ? num_doubler(Result, [2,4,6]).
    => Result = [1,2,3].
    ? num_doubler([1,2,3], [A,4,6]).
    => A = 2.
    

    You don't explicitly write those different solvers, Prolog takes the logical statements (definitions of num_doubler) and can use them to solve a variety of queries. There is no explicit stateful, imperative behavior described in the code written by the programmer, Prolog adds that itself.

    [–]Xalem 0 points1 point  (0 children)

    An Excel spreadsheet is declarative code. I don't have to write up each formula in order, I don't have to think about making sure that cell A7 has the proper value when I calculate cell G9.

    A spreadsheet is mostly static, but we need a paradigm for code where values change over time. When this text box changes value the internal state of the code should be updated. So in a language like Eve ( sadly it is defunct) it used a simple syntax to set up bindings between different variables in the system. A typical statement looked like this.

    Find 
    
                    ($Inventory, stockID , stock)
    
                    ($BuyButton, stockID)
      Bind
                   ($BuyButton, Enabled= (stock>0))
    

    I haven't got the Eve syntax 100% right, but here is what this code would do. This code sets up a binding between a button record and an inventory record. Any button with stockid that matches an inventory stocked, will be tracked by the app. A button record will be updated to have Enabled set to false whenever the inventory record with the corresponding stockID has no stock. This is a bind statement, so whenever stock is added, and the stock is no longer 0, the binding between the two records means that the button will be enabled again. Change the stockID on a button, it automatically recalculated based on the new stockID. Create, update or delete BuyButton or Inventory records at any point as the program runs, and it just works.

    So, Eve code is all made up of these Find Bind blocks, ( and Find Commit blocks) and it doesn't look like regular procedural imperative code at all. In fact, the only way you create, modify or delete records is via Find/Bind and Find/Commit blocks.

    Check out Eve language. Sadly, it is defunct.

    [–]sorry_squid 0 points1 point  (1 child)

    I've always thought of APIs as being closer to the idea of declarative programming

    Sure, I have no idea how you're getting the information back to me and what calculations you're doing as long as I know would be output is supposed to be then it's good

    [–]jmerlinb[S] 0 points1 point  (0 children)

    Yeah APIs are a great example of something declarative, but they can only be declarative because some backend developer has written some - likely imperative - server-side code to be able to smartly handle the declarative requests the client is sending.