What I have learned is that what makes a language (or platform, or tool) "good" isn't how easy it is to write good code, but how hard it is to write bad code.
Does a beginner following the path of least resistence, on a tight schedule, end up with maintainable code or not?
I'd argue that this is the weakness with (the traditional) OO languages. An experienced developer with plenty of time can write good software with almost any tool. But that's not what's interesting. I want to see tools that not just lets but rather guides inexperienced developers into making maintainable software.
Things like "no nulls" or "immutable by default" in Rust and most functional languages are two examples of such designs. OO itself doesn't necessarily mean the developers get trapped in poor code, but the traditional 3 (C++, Java, C#) sure do give developers lots of guns to shoot at their feet. Perhaps not mainly because they are OO, but because they inherited some poor fundamental decisions about mutability and nulls (from C) and about inheritance as a default method of abstraction (from C++) etc.
>isn't how easy it is to write good code, but how hard it is to write bad code
In many languages they make it harder to write bad code but they do so at the expense of delivering functionality quickly. There's a clear trade off there - one which is often very project dependent (some projects primarily need to develop functionality quickly; others prioritize stability).
The problem of slowing down development becomes particularly pronounced when developing something where the biggest risk isn't building the thing wrong, but building the wrong thing. It's extremely expensive to use a very strict language if you're developing code where requirements are highly in flux and cannot be discerned up front.
Ideally general purpose languages should enable a smooth transition from coding up a prototype/MVP all the way to production-hardened, spotlessly clean code.
I like rust a lot but I would only use it to write very low level code where the requirements are cast in stone and speed and stability is of the utmost importance. It's a lovely language but it's very slow to build stuff in, and in almost all cases it should be used to rewrite something existing rather than to build it anew.
I agree - in a prototyping phase it may be beneficial to be able to throw something together. The problem is that what you throw together is invariably the base for the real thing. The motto of "design one to throw away" is great, but I haven't seen it applied as much as it should.
Using a "stiff" set of tools might slow down the first stages of prototyping, but it also makes the prototype easier to modify as requirements change. The question I suppose is simply where the equilibrium occurs. I.e. does Rust or F# make a thing that is easier to modify (because of less coupling) already after one or two months, or only after one or two years?
>The problem is that what you throw together is invariably the base for the real thing.
This isn't invariable at all. More often what you throw together gets thrown away. I'd estimate this happens to more (working) code I've written over my career than not. The hardest thing to get right is often getting the contours of a tool right and ascertaining what it should do - not making it work right after it has proven itself.
>Using a "stiff" set of tools might slow down the first stages of prototyping, but it also makes the prototype easier to modify as requirements change.
This is only really the case where the prototype was fundamentally solving the right problem in the right way to begin with and the subsequent changes are incremental in nature. If the design of, say, a microcomponent is flawed from the outset or it did the wrong thing and you have to re-do it, those "stiff" tools slow you down.
Using an extremely strict set of tools in a prototyping environment also invariably means a lot of extra up front work dealing with the tools' attempts to protect you from bugs which have an extremely low probability of occurring and/or an extremely low cost when they do occur.
If F# or rust or haskell really was quicker and more effective in a prototyping environment as well as when writing production hardened code, programmers would likely eventually converge on only using them. That isn't what is happening.
> If F# or rust or haskell really was quicker and more effective in a prototyping environment as well as when writing production hardened code, programmers would likely eventually converge on only using them. That isn't what is happening.
This does not seem to me to be a safe assumption to make. The market is irrational. There is no reason to believe popularity has any significant correlation to the effectiveness of a tool.
To some degree. But programmers are not, in general, easily herded, even by other programmers.
> There is no reason to believe popularity has any significant correlation to the effectiveness of a tool.
Programmers are not stupid. They are not totally ineffective at pushing management to use sane tools. One or both of those would have to be true for your statement to be true.
Well, you may say, it's a slow process. But Haskell has been out there for more than 30 years. F# is 14 years old, and supported by Microsoft. These languages have had plenty of time for programmers to notice that they were actually more effective in the real world.
> This is only really the case where the prototype was fundamentally solving the right problem in the right way to begin with and the subsequent changes are incremental in nature. If the design of, say, a microcomponent is flawed from the outset or it did the wrong thing and you have to re-do it, those "stiff" tools slow you down.
This. So, so, so, so much this.
I absolutely agree that getting the contours of the tool right (nice analogy there) is (one of) the hardest parts. The main issue I find when using strict, type-driven FP langs for "first draft" style implementations is that I waste time with the unavoidable ceremony that most of these languages require, when what I really want to be doing is probing around to discover the rough edges. The "my last name is Curry" style of FP almost requires you to declare these edges up front as you code.
I actually find that TDD is even more useful in FP contexts than in procedural - mainly because a) it's a good way to help think about the shape of the tool before building it, and b) the FP implementations are often more mentally complex with recursion, pattern matching, and other such things that (IMO) require more brainpower to grok than simple procedures, and to be honest I just find myself needing the tests so I don't go mad trying to be a human compiler. I think even the most staunch TDD fanatics wouldn't try to argue that it's a fast way to prototype.
These days I find myself reaching for dynamic languages with gradual typing for prototypes that might hit production (JS+TS, Python, even PHP). When I'm satisfied with the general shape, I'll add a few type hints here and there to make my IDE friendlier. After a while, usually at the point where there are large additions to requirements, I'll find myself rewriting at least a chunk of the original in a completely different manner, usually in a more type-driven manner, usually with a more FP slant, and often in a different language with more strictness.
I would like to see more mainstream languages support both type-driven FP and simple procedural code without using Haskell-inspired syntaxes or turning into the incomprehensible mess that is Scala. Java is slowly morphing that way, but it still lacks some of the FP fundamentals for when you do want to go full-zealot.
> some projects primarily need to develop functionality quickly; others prioritize stability
But they get the ballance completely wrong. In Python, I can just write code that returns some object or None (Python equivalent of null). In Java/C/C++ I can't - I have to appease the type system (i.e. declare the return type), but there is also no way to even later tell the compiler that it can't be null!
I'd argue that this is the weakness with (the traditional) OO languages. An experienced developer with plenty of time can write good software with almost any tool. But that's not what's interesting. I want to see tools that not just lets but rather guides inexperienced developers into making maintainable software.
Things like "no nulls" or "immutable by default" in Rust and most functional languages are two examples of such designs. OO itself doesn't necessarily mean the developers get trapped in poor code, but the traditional 3 (C++, Java, C#) sure do give developers lots of guns to shoot at their feet. Perhaps not mainly because they are OO, but because they inherited some poor fundamental decisions about mutability and nulls (from C) and about inheritance as a default method of abstraction (from C++) etc.