4 ms·
The longer I’m in this industry, the more I find that there are two types of programmers: those who default to writing every program procedurally and those who
by stouset 2y ago
The longer I’m in this industry, the more I find that there are two types of programmers: those who default to writing every program procedurally and those who default to doing so declaratively.
The former like to brag about how quickly they can go from zero to a working solution. The latter brag about how their solutions have fewer bugs, need less maintenance, and are easier to refactor.
I am squarely in the latter camp. I like strong and capable type systems that constrain the space so much that—like you say—the implementation is usually rote. I like DSLs that allow you to describe the solution and have the details implemented for you.
I personally think it’s crazy how much of the industry tends toward the former. Yes there are some domains where going the time from zero to a working product is critical. And there are domains where the most important thing is being able to respond to wildly changing requirements. But so much more of our time and energy is spent maintaining code than writing it in the first place that upfront work like defining and relating types rapidly pays dividends.
I have multiple products in production at $JOB that have survived nearly a decade without requiring active maintenance other than updating dependencies for vulnerabilities. They have had a new version deployed maybe 3-5 times in their service lives and will likely stay around for another five years to come. Being able to build something once and not having to constantly fix it is a superpower.
- ninetyninenine 2y agoIt’s not crazy, you have a million instructions and you’re just going to write all of that out in a single declaration or a list of procedures? First off the declaration is better as it’s less error prone but it comes at the cost of being harder to write and harder to interpret. Imagine if we communicated with one declarative run on sentence. No paragraphs at all… Procedures are default and easier to our nature as humans.
- dasil003 2y ago> Yes there are some domains where going from the time from zero to a working product is critical. And there are domains where the most important thing is being able to respond to wildly changing requirements I agree with your observations, but I'd suggest it's not so much about domain (though I see where you're coming from and don't disagree), but about volatility and the business lifecycle in your particular codebase. Early on in a startup you definintely need to optimize for speed of finding product-market fit. But if you are successful then you are saddled with maintenance, and when that happens you want a more constrained code base that is easier to reason about. The code base has to survive across that transition, so what do you do? Personally, I think overly restrictive approaches will kill you before you have traction. The scrappy shoot-from-the-hip startup on Rails will beat the Haskell code craftsmen 99 out of 100 times. What happens next though? If you go from 10 to 100 to 1000 engineers with the same approach, legibility and development velocity will fall off a cliff really quickly. At some point (pretty quickly) stability and maintainability become critical factors that impact speed of delivery. This is where maturity comes in—it's not about some ideal engineering approach, it's about recognition that software exists to serve a real world goal, and how you optimize that depends not only on the state of your code base but also the state of your customers and the business conditions that you are operating in. A lot of us became software engineers because we appreciate the concreteness of technical concerns and wanted to avoid the messiness of human considations and social dynamics, but ultimately those are where the value is delivered, and we can't justify our paychecks without recognizing that.
- stouset 2y agoSure it’s important for startups to find market traction. But startups aren’t the majority of software, and even startups frequently have to build supporting services that have pretty well-known requirements by the time they’re being built. We way overindex on the first month or even week of development and pay the cost of it for years and years thereafter.
- sfn42 2y agoI'm not convinced that this argument holds at all. Writing good code doesn't take much more time than writing crap code, it might not take any more time at all when you account for debugging and such. It might be flat out faster. If you always maintain a high standard you get better and faster at doing it right and it stops making sense to think of doing it differently as a worthwhile tradeoff.
- mithametacs 2y agoThat's the hard part of project management. Is it worth spending a bit more time up-front, hoping to prevent refactoring later, or is it better to build a buggy version then improve it? I like thinking with pen-and-paper diagrams; I don't enjoy the mechanics of code editing. So I lean toward upfront planning. I think you're right but it's hard to know for sure. Has anyone studied software methodologies for time taken to build $X? That seems like a beast of an experimental design, but I'd love to see.
- sfn42 2y agoI personally don't actually see it as a project management issue so much as a developer issue. Maybe I'm lucky but in the projects I've worked, a project manager generally doesn't get involved in how I do my job. Maybe a tech lead or something lays down some ground rules like test requirements etc but at the end of the day it's a team effort, we review each other's code and help each other maintain a high quality. I think you'd be hard pressed to find a team that lacks this kind of cooperation and maintains consistently high quality, regardless of what some nontechnical project manager says or does. It's also an individual effort to build the knowledge and skill required to produce quality code, especially when nobody else takes responsibility of the architectural structure of a codebase, as is often the case in my experience. I think that in order to keep a codebase clean you have to have a person who takes ownership of the code as a whole, has plans for how it should evolve etc. API surfaces as well as lower level implementation details. You either have a head chef or you have too many cooks, there's not a lot of middle ground in my opinion.
- jfwuasdfs 2y agoAgreed 100%. Other benefit is fearless refactoring since API changes appear as type errors. With no type-checking you need to gamble testing all codepaths.
- dismalaf 2y ago> I personally think it’s crazy how much of the industry tends toward the former. It's because most people who use technology literally don't care how it works. They have real, physical problems in the real world that need to be solved and they only care if the piece of technology they use gives them the right answer. That's it. Literally everything programmers care about means nothing to the average person. They just want the answer. They might care about performance if they have to click the same button enough times, and maybe care about bugs if it's something that is constantly in their face. But just working is enough...
- tyingq 2y agoAnd often, how fast they have some mvp thing in their hands...the better. Maybe not actually better, but politically better or whatever. Quality, maintainability, etc, is less important in that moment. And, like many things in companies, short term desires dominate.
- nine_k 2y ago> they only care if the piece of technology they use gives them the right answer. A poorly typed program often would give a wrong answer, or, in a less dangerous case, crash. Same for physical parts: you want them to solve some business problem, but often you won't go for the absolute cheapest, because they may work poorly.
- dismalaf 2y agoI'm thinking more along the lines of how scripting languages are often used in, say, scientific domains (Python, R, etc...). Or how JavaScript and Ruby are more popular than, say, Rust and Haskell for startups. "Poorly typed" means different things to different people, in the context of this article and thread it would probably mean weakly typed or dynamically typed? Which has nothing to do at all with the correctness of a formula or what output a program will produce.
- layer8 2y agoWhat does strong typing have to do with procedural vs. declarative? IMO strong typing is beneficial regardless.
- mithametacs 2y agoGrandparent was such a good post otherwise! I love strong types. I love for loops. I love stacks. GP! Try Rust. Imperative programming isn’t orthogonal to types. You can go hard in Rust. (I loved experimenting with it but I like GC) GP! Try data driven design. Imperative programming isn’t orthogonal to declarative. Real talk, show me any declarative game engine that’s worth looking at. The best ones are all imperative and data driven design is popular. Clearly imperative code has something going for it. and the advantages aren’t strictly speed of development, but imperative can be clearer. It just depends.
- stouset 2y agoI adore Rust. My point isn’t that you can’t have both, but that the two types of programmers have different default approaches to problem solving. One prefers to model the boundaries of domain as best they can (define what it should look like before implementing how it works), one prefers to do things procedurally (implement how it works and let “what it looks like” emerge as a natural result). Neither is strongly wrong or right, better or worse. They have different strengths in different problem areas, though I do think we’ve swung far too hard toward the procedural approach in the last decade.
- mithametacs 2y agoI agree with the distinction in approaches. In other words, it sounds like you're distinguishing agile and waterfall. I just find it odd to analogize: > agile : waterfall :: imperative : declarative
- stouset 2y agoEither you misread something or I communicated poorly.
- rendaw 2y agoI think GPs point is that they haven't gone from zero to a working solution, they've gone from zero to N% towards a working solution and then slowed down everyone else. Maybe for the most trivial programs they can actually reach a solution. You can't write a program without knowing that x is a string or a number, your only choice is whether you document that or not.
- mithametacs 2y agoYes you can, you handle every case equally. You don’t even need the reflection mechanisms to be visible to the user with a good type system. A good type system participates in codegen. for a really simple example: languages which allow narrowing a numeric to a float, but also let you interpolate either into a string without knowing which you have. A statically typed Console.log in JS/TS would be an unnecessary annoyance.
- throwaway2037 2y ago> those who default to writing every program procedurally and those who default to doing so declaratively. What is the difference between programming procedurally and programming declaratively? I saw these terms used this way.
- Mikhail_Edoshin 2y agoIt is actually a rather hard question. There is a web page somewhere where the author asks it, lists possible answers and get amazed by some of the definitions, such as "declarative is parallelizable". Cannot find it now, unfortunately. I would say that imperative is the one that does computation in steps so that one can at each step decide what to do next. Declarative normally lacks this step-like quality. There are non-languages that consist solely of steps (e.g. macros in some tools that allow to record a sequence of steps), but while this is indeed imperative, this is not programming.
- stouset 2y agoHere’s my personal attempt at a definition. One side cares more about how the solution is implemented. They put a lot of focus on the stuff inside functions: this happens, then that happens, then the next thing happens. The other side cares more about the outside of functions. The function declarations themselves. The types they invoke and how they relate to one another. The way data flows between parts of the program, and the constraints at each of those phases. Obviously a program must contain both. Some languages only let you do so much in the type system and everything else needs to be done procedurally. Some languages let you encode so much into the structure of the program that by the time you go to write the implementations they’re trivial.
- mithametacs 2y agoSounds like a page worth another search if you don't mind. I'll give you Internet Points in return! -- my attempt: Imperative defines the order and semantics of each step. Declarative defines the prerequisites and intent of each step. The algorithms each can implement are equivalent.
- DontchaKnowit 2y agoI am also squarely declarative, but currently use a language for work that forces me to be procedural pretty much always and it kinda sucks. My code always feels bad to me and the cognitive load is always super high
- eyelidlessness 2y agoIs it the language that forces procedural code? In my experience it’s usually the stdlib, but the language itself is capable of declarative constructs outside of existing APIs. If that’s the case, an approach like “functional core, imperative shell” is often a good one. You can treat the stdlib like it’s any other external API, and walk it off as such.
- DontchaKnowit 2y agoThere is no stdlib. Its a very specific proprietary purpose built language thats been around since like the 90s. It has a super limit set of standard functions that operate on an underlying proprietary data structure and every thing else is just a thin vaneer over a very limited set of c functions.
- Mikhail_Edoshin 2y agoDeclarative programming is essentially programming through a parameter. The declaration is that parameter that will be passed to some instruction. In small doses declarative programming occurs with every function call. In declarative programming the parameter is essentially the whole program and the instruction is implicit; we know more or less how it works, but generally assume it just exists or even forget about it and take it as the way things work. Of course declarative programming is simpler and less error prone. But it is also essentially inflexible. The implicit instruction is finite and will inevitably run into a situation when the baked execution logic does not quite fit. It will be either inefficient or require a verbose and repetitive parameter, or just flat out incapable of doing what is desired. In this case declarative programming fails; it is impossible to fix unless we rewrite the underlying instruction. E.g. 'printf' is a small example of declarative programming. It does work rather well, especially when the compiler is smart about type checks, but once you want to vary the text conditionally it fails. (The thing that replaces 'printf' are template engines that basically reimplement same logic and control statements you already have in any language and the engine works as an interpreter of that logic. The logic is rather crude and limited and the finer details of formatting are left to callbacks that are mostly procedural.) For example, how do I format a list so that I get "A" for 1, "A and A" for 2, and "A, A, and A" for more? Or how I format a number so that the thousand separator appears only if the number is greater than 9999? Or what to do if I have an UTF-8 output, but some strings I need to handle are UTF-16? The existing declarative way did not foresee these cases and to add them to the current model would complicate it substantially. But if I have a simple writer that writes basically numbers and strings I can very quickly write procedures for these specific cases. Instructions are primary by their nature. A piece of data on its own cannot do anything. It always has an implicit instruction that will handle it. So instructions are the things we have to master.
- RadiozRadioz 2y agoI think TypeScript is part of the problem here. It's a thin layer atop a dynamically typed language with giant escape hatches and holes. I think it's great if you're stuck in JS, it's so much better than JS, but I can't think why anyone would choose it compared to a "real" statically typed language.