DEV Community

Discussion on: Declarativity Comes at a Price

Collapse
 
kspeakman profile image
Kasey Speakman • Edited

I think there are a few branches to this topic of "declarative code" and they should not be lumped together.

The shiniest example given is that of map and filter. Operations like these bring a large value with minimum cost to understand and maintain. These operations transcend language and platform, being rooted generally in math. So you can find them in a lot of different languages and at different levels. E.g. SQL SELECT is map, WHERE is filter, etc.

Another possible interpretation is using a framework with conventions, so you can declare things instead of writing procedural code. Some examples might be ORMs where you use an attribute to declare a property as a primary key. Or a Dependency Injection framework that automagically wires up interfaces with an implementation at run time. These start heading down the path of requiring specialized and/or non-portable knowledge. Advanced usage sometimes ends up being as much or more work as doing things manually.

Yet another variation I can think of is the tendency as developers to over-abstract. This is easiest (but by no means limited to) early in a programmer's career. We play the "what if" game, and program for a number of scenarios that never actually happen. This can extend to implementing an entire framework around our code, ostensibly to make it easier to add new use cases. But the custom framework just gets in the way of those use cases you couldn't predict. It becomes a case of designing the plumbing first and building a house around it.

So I would rank how desirable it is to be "declarative" differently between these cases. Common declarative operations like map and filter, it is hard to see any significant downside to using them. Published "frameworks", although there are probably some good ones, I tend to avoid these in favor of a "library" of operations (e.g. Dapper instead of EntityFramework). For plumbing code that I write, I usually find it best to avoid prescriptive abstractions. Instead I try to keep things course grained. When several use cases have some logic overlap, I'll develop a "helper" function for that combination of operations. That way new use cases can choose to call a helper if it fits, but can fall back to managing everything itself if needed.

Collapse
 
oleggromov profile image
Oleg Gromov

Kasey that's a perfect complementary comment, thanks!
Hope you don't mind if I use your points to make my reasoning more concise and useful.

The thing that inspired the article is a mix of your first example related to "advanced usage" of a declarative tool with a temptation to over-abstract. In my project, I had different consequent and/or parallel database requests to be made on different API calls. After writing the 3rd function performing db requests in another sequence but still very similar manner, I realized that it might be a good chance to step ahead of current problems and solve it on a higher level.

I decided to make all possible combinations of subsequent and parallel requests configurable with support for result passing between them. The process was complicated with the fact that at the moment I tried to use promises and callbacks together producing horrible code style I couldn't understand.

So I tried to make a barely readable config of requests, queries to perform, their parameters and error messages; a bit cryptic code to perform required function calls against the config; and deleting code I didn't like in the first place. The latter in fact was bad but not because of the imperativity but because of the weird mix of Promises, which is a javascript abstract object incapsulating an async action behind the interface to handle its results, and callbacks, which was a usual way of writing asynchronous code without any libraries a while ago.

The "declarative" solution I ended up with was even worse than the initial code I tried to make prettier and better. Here's where my point about writing your own declarative libraries/frameworks/whatever takes effect. It is hard because understanding abstraction takes time and effort to make, and so does the actual coding of the declarative tool.

Did I try to solve an existing problem? Yes, I did, but the problem lied not in a plain of declarativity/imperativity.

Could declarative code help? It might have helped if I used an existing query-chaining library. Writing my own library would take so much time - and the code I wrote in a few hours hadn't really solved a problem, not even awkwardly.

Did I try to over-abstract hiding requests complexity in a config? I definitely did.

Finally, by getting rid of callbacks and leaving only Promises, I managed to make the code very readable and clean. There are rather complicated use cases like user deletion or list creation, which include simultaneous DELETE or INSERT affecting a few tables, waiting for the results and performing operations on another table, or simple ones like list retrieval.

Collapse
 
kephas profile image
Nowhere Man

What I get from this is that you tried do transform code that was problematically promise-heavy to a more declarative form and that form didn't solve anything.

Am I missing something?

Because the only takeaway I see is that transforming code to a declarative form isn't magical in that it would solve any deficiency of the underlying code.

I don't see how that supports the notion that declarative code might be too costly.

Thread Thread
 
oleggromov profile image
Oleg Gromov • Edited

Declarative code itself might be not pricey at all - it's clean and nice, no problems.

My example shows that sometimes transformation to declarativity might cost a lot - because it is hard to make a generalized solution. Could I make a config to perform request in any possible variations? Yes, definitely. Is it worth it? Well, that depends on the use cases, your will and budget to make an abstract solution and whatsoever.

Perform a mental experiment: when you use functional and declarative lodash or underscore it costs you nothing on the surface. But somebody spent substantial time separating, coding and debugging primitives you use. It must sound undoubtedly true. And then imagine your project and you, willing to write declarative nice code but lacking needed libraries. Won't writing a one on your own be way to expensive? It might - that's my point.

Either way, there're perfect examples in the comments related to hidden complexity of the computations or too domain-specific solutions like React, for example.

Thread Thread
 
kephas profile image
Nowhere Man

But transforming to any different form might cost a lot. So again, it says nothing about declarativity and only about code transformation.

Transforming code to CPS might cost a lot. Transforming code to SSA might cost a lot. Transforming code to OOP might cost a lot. etc…

But writing declaratively from the start? Well, then the odds that it would cost anything substantial are significantly lower!

I wouldn't start by writing a whole library, I would just write the declarative functions I need. That's actually how functional programmers work… And it's not hard when you are used to it.

Like structured programming, OOP, concatenative programming, logic programming. Like any style, actually.

Collapse
 
kspeakman profile image
Kasey Speakman

Awesome, glad to contribute to the discussion. :) Thanks for the post!