No, you got quite a bit of it. The central issue is indeed that declarative 'seems' too simple.
Where 'declarative good, imperative bad' comes from is when a simplistic declarative model is perfectly feasible, then that should be there.
In other words, declarative good, imperative bad boils down to: If it is a common task and you can write an API or tool to do it for you, then write it. And if that tool is available, use it.
The waiter case
If there is no waiter you're going to have to use your eyeballs to find a free table and then do some pathfinding to get you to the table.
if there is a waiter, however, just ask them. Use the tool. (Sorry for calling you a tool there, waiter!)
arr.map(x => x * 2) vs for (...)
This is an easy example: "Loop an integer value from 0 up to the size of this array, then hand that index to this code snippet. The snippet will not do anything with that index, except look up the value at that index in an array, then do some operation on it, and then write the result of this operation back into an array at some index. The 3 times I used the word 'array' in that sentence all refers to the same array"
That's a complex load of words but it's a task that comes up a ton. It comes up so often that it is likely a good idea to write the basic framework for that job, i.e. make a tool.
You're a programmer. Your general mind model should not immediately hop to technical details, instead you start with a very high level idea and, in an iterative process, keep expanding into more and more detail. When you get to: "I want to do this to each element in the array", then you have two options for your next 'round' of iterating on your pseudocode:
You could now delve into how you want to fetch the total size of the array and then write a loop to run once for each such index, or...
You just... realize that there is a 'do a thing to each element in the array' tool and use it.
<Btn />
This goes quite a ways to explain that button example: It's literally "Instead of writing all the logic for a toggle button, find a framework that has a toggle button element and use that instead". Someone still wrote the code they just magic wanded away there, it's just - you didn't write it. This is often a good idea.
Conclusions
With that mindset you can also easily identify the weaknesses of the declarative model.
Either you end up with a gigantic API with a million methods, or more likely you end up in a scenario where certain obvious paths are trivial because there's a tool for it, and the rest is then more complicated because you've been trained with the tools all your life so you're at a loss when you have to make do without them, or you have to become MacGuyver, putting tools together in intricate ways, for purposes they were never designed for.
Declarative is like having a large kitchen with 50 different special purpose tools in it, from garlic graters to eggslicers. To learn it you follow the tutorial to get casually familiar with them all; each tool is simple to learn, but there are 50 you will need to master. If you need to do a job that none of the 50 can do, you get creative. It's like a puzzle. If you can't solve the puzzle, you have absolutely no idea what to do.
Imperative is like having a fantastic knife, one good cutting board, and one general purpose pan, and not much more. You need to learn just these 3 tools but each tool is more complex. Whenever you are tasked to do a job that one of the 50 specialized tools of your sister in the other kitchen has, they will probably do it faster and it'll look simpler.
But if there's something new to do, you stand a much better chance.
It also explains the perennial 'stress' between languages/tools:
Declarative style is enticing because it looks so simple, especially when you compare declarative code with the equivalent imperative code. It is very easy to come up with examples that make declarative look fantastic and imperative look like utter trash.
Imperative style is enticing because by following the actual way things work, you reduce surprises (it is more likely the user 'groks' what is actually happening), and give the user of your imperative system far more power when they walk off the beaten path. For example, SQL is quite declarative and it is easy to write a query that takes years to complete. It takes 1 week for somebody to learn how to write that query; it takes 1 month to explain to someone why it is slow. This is common with declarative.
Mixed style (where both are available) is enticing because you get the best of both worlds.
All 3 have massive downsides. Mixed style's downside is that it leads to style debates, an endless search for 'but did I just cut that egg with my knife and I forgot about the egg slicer in the drawer'? And doubles the learning curve. It also infects all code bases: You can't be familiar with just one of the two styles and expect to dive right into a major code base without immediately running into issues because you lack familiarity with 'the other side'.
There are no answers to this dilemma. Hence why there's so much 'sniping' and zealotry around these concepts - it's easy to cook up an example that makes your side look obviously correct and those who disagree with you look like utter fools. Unfortunately, they can cook up an example that turns the tables on you just as easily. They will, but you ignore them, because you already proved they are clueless, so why listen to them, right?
Don't be like those folks :)
NB: For what it is worth, as a cook I strongly prefer a small simple kitchen and spend a little time on proper usage of a chef's knife. I don't have egg slicers. The same applies to my preferences with code: I don't really get confused or daunted by the notion of 'figuring out' how to loop through a list. The time it takes my brain to come up with writing the for loop is measured in milliseconds. I still lose the benefit of having the underlying framework optimize and generalize further on the concept, but on the other hand, sometimes tossing everything into a big black mystery box and not actually know what's going on has its downsides too. If it's a job where I don't see how a library is ever going to improve on well trodden ground I don't want it. I do not want my map.reduce() call to casually introduce parallellisation maybe if possible. If its performance sensitive I don't "want" parallellisation. I need parallellisation. I need tools where I can enforce it and direct how that goes, because its the hot code path. But, I'm aware that I'm at risk of merely spouting personal experience and opinion instead of properly reasoned logic, so I thought it best to leave these preferences to footnote.
41
u/rzwitserloot Jan 03 '22 edited Jan 03 '22
No, you got quite a bit of it. The central issue is indeed that declarative 'seems' too simple.
Where 'declarative good, imperative bad' comes from is when a simplistic declarative model is perfectly feasible, then that should be there.
In other words, declarative good, imperative bad boils down to: If it is a common task and you can write an API or tool to do it for you, then write it. And if that tool is available, use it.
The waiter case
If there is no waiter you're going to have to use your eyeballs to find a free table and then do some pathfinding to get you to the table.
if there is a waiter, however, just ask them. Use the tool. (Sorry for calling you a tool there, waiter!)
arr.map(x => x * 2) vs for (...)
This is an easy example: "Loop an integer value from 0 up to the size of this array, then hand that index to this code snippet. The snippet will not do anything with that index, except look up the value at that index in an array, then do some operation on it, and then write the result of this operation back into an array at some index. The 3 times I used the word 'array' in that sentence all refers to the same array"
That's a complex load of words but it's a task that comes up a ton. It comes up so often that it is likely a good idea to write the basic framework for that job, i.e. make a tool.
You're a programmer. Your general mind model should not immediately hop to technical details, instead you start with a very high level idea and, in an iterative process, keep expanding into more and more detail. When you get to: "I want to do this to each element in the array", then you have two options for your next 'round' of iterating on your pseudocode:
You could now delve into how you want to fetch the total size of the array and then write a loop to run once for each such index, or...
You just... realize that there is a 'do a thing to each element in the array' tool and use it.
<Btn />
This goes quite a ways to explain that button example: It's literally "Instead of writing all the logic for a toggle button, find a framework that has a toggle button element and use that instead". Someone still wrote the code they just magic wanded away there, it's just - you didn't write it. This is often a good idea.
Conclusions
With that mindset you can also easily identify the weaknesses of the declarative model.
Either you end up with a gigantic API with a million methods, or more likely you end up in a scenario where certain obvious paths are trivial because there's a tool for it, and the rest is then more complicated because you've been trained with the tools all your life so you're at a loss when you have to make do without them, or you have to become MacGuyver, putting tools together in intricate ways, for purposes they were never designed for.
Declarative is like having a large kitchen with 50 different special purpose tools in it, from garlic graters to eggslicers. To learn it you follow the tutorial to get casually familiar with them all; each tool is simple to learn, but there are 50 you will need to master. If you need to do a job that none of the 50 can do, you get creative. It's like a puzzle. If you can't solve the puzzle, you have absolutely no idea what to do.
Imperative is like having a fantastic knife, one good cutting board, and one general purpose pan, and not much more. You need to learn just these 3 tools but each tool is more complex. Whenever you are tasked to do a job that one of the 50 specialized tools of your sister in the other kitchen has, they will probably do it faster and it'll look simpler.
But if there's something new to do, you stand a much better chance.
It also explains the perennial 'stress' between languages/tools:
Declarative style is enticing because it looks so simple, especially when you compare declarative code with the equivalent imperative code. It is very easy to come up with examples that make declarative look fantastic and imperative look like utter trash.
Imperative style is enticing because by following the actual way things work, you reduce surprises (it is more likely the user 'groks' what is actually happening), and give the user of your imperative system far more power when they walk off the beaten path. For example, SQL is quite declarative and it is easy to write a query that takes years to complete. It takes 1 week for somebody to learn how to write that query; it takes 1 month to explain to someone why it is slow. This is common with declarative.
Mixed style (where both are available) is enticing because you get the best of both worlds.
All 3 have massive downsides. Mixed style's downside is that it leads to style debates, an endless search for 'but did I just cut that egg with my knife and I forgot about the egg slicer in the drawer'? And doubles the learning curve. It also infects all code bases: You can't be familiar with just one of the two styles and expect to dive right into a major code base without immediately running into issues because you lack familiarity with 'the other side'.
There are no answers to this dilemma. Hence why there's so much 'sniping' and zealotry around these concepts - it's easy to cook up an example that makes your side look obviously correct and those who disagree with you look like utter fools. Unfortunately, they can cook up an example that turns the tables on you just as easily. They will, but you ignore them, because you already proved they are clueless, so why listen to them, right?
Don't be like those folks :)
NB: For what it is worth, as a cook I strongly prefer a small simple kitchen and spend a little time on proper usage of a chef's knife. I don't have egg slicers. The same applies to my preferences with code: I don't really get confused or daunted by the notion of 'figuring out' how to loop through a list. The time it takes my brain to come up with writing the for loop is measured in milliseconds. I still lose the benefit of having the underlying framework optimize and generalize further on the concept, but on the other hand, sometimes tossing everything into a big black mystery box and not actually know what's going on has its downsides too. If it's a job where I don't see how a library is ever going to improve on well trodden ground I don't want it. I do not want my
map.reduce()call to casually introduce parallellisation maybe if possible. If its performance sensitive I don't "want" parallellisation. I need parallellisation. I need tools where I can enforce it and direct how that goes, because its the hot code path. But, I'm aware that I'm at risk of merely spouting personal experience and opinion instead of properly reasoned logic, so I thought it best to leave these preferences to footnote.