I've met many senior engineers (myself included) who have made the opposite transition (dynamic -> static) and now the thought of using a dynamic language for anything outside of small scripts is uncomfortable.
90% of the issues that come up on the teams I've been a part of are caused by developers who are lacking architecture (and related) skills, regardless of the language we're working in. They struggle to find the core metaphors for the problem space they're working in, they struggle to name things, they struggle to find clear and effective refactor points, they struggle to consider the medium and long term implications of their decisions, they struggle to organize code well, etc.
(The other 10% of issues are constantly changing business requirements.)
Static vs. Dynamic has nothing to do with those problems — they all exist in both worlds.
Personally, my most recent stint with a statically typed language (Swift) was hella frustrating. I felt like I was fighting the type system all the time. (Several of my biggest complaints have been address since I moved on from Swift, to be fair). But some people just love it — and good for them!
For me, Ruby is the closest language to how my brain works. It has the least friction between Idea and Running Code. I hope to write in Ruby (or similar) for the rest of my life.
If I really want a static analyzer for my Ruby, I can try Sorbet. If I desperately need to be running a compiled binary for speed or flexibility, I'll have to choose something else. These are just the tradeoffs.
The world is a big place. I'd want everyone to be able to find a niche where you can write the code you want in the language(s) you want.
Watching juniors at work, I find that it takes them less time to complete a task in dynamic languages, but I think they get better results when the language forces them to think a bit harder.
I'm in a bit of a minority on gradual typing. Give me static types or none at all. Python's approach to typing gives me the heebie-jeebies.
I use Go at work but mostly Python for personal projects currently, and am pushing towards Clojure for personal projects in the future. But that’s because personal programming for me is a cathartic escape from the straitjacket of zero abstraction power and the tedium of manual error handling; I care about my own satisfaction more than I care about scalability to a team of varying skill levels in that context.
I'd happily use Elixir as well, another dynamic language, if I had that option and couldn't use Clojure.
Only when I can't use those would I go back to Java, Kotlin, Scala, C# or C++ (all which I have prior experience with).
With static languages, on the other hand, writing routine things becomes a chore and people end up needing crutches in the form of advanced IDEs to generate boilerplate code that is a pain to read because of templating and such. Of course, error messages when templating/generics becomes involved are just horrible. Without generics you tend to lose a bit of the benefit of statically typed languages.
At the end of the day it's the size of the codebase, team discipline, and an emphasis on simplicity that matters more.
It is my experience, though, that people start out enamored of dynamically typed languages because of their expressivity and high productivity but then end up gravitating towards statically typed languages for large codebases, because they are more willing to trade convenience and coolness for safety and reliability the more senior they are.
For context, I usually work in <50 size engineering dpts, if not companies. If you're scaling to thousands you can spare the scale, probably.
Then there is the debugging support in Visual Studio Full. Yes, there is debugging in vs code / typescript, but it is far away from how easy it is to debug a C# application in Visual Studio. Too often I have to resort to printf type of debugging to gain insight into which code line was the last line to run before the error.
Yes, I like dynamic languages because some of your things you can do with them are freaking awesome, but at the end of the day I like the comfort feeling that Visual Studio and a static language provides.
I'm happy to use Python for a lot of stuff. I also use C or C++ for a lot of stuff. It's usually pretty obvious what the right choice is.
Using a more dynamic language for larger projects takes more discipline, and that effort reduces the productivity that is otherwise the hallmark of dynamic typing. There's a point where static typing becomes a net win.
For me, Python's type annotations extend that cross-over point. Both as documentation for humans, and driving IDE or CI-based type checks, I think they've made Python more scalable.
If I've been doing heavy C++ work for a while, switching back to Python is like throwing off a whole lot of bureaucratic overhead: it feels like you can turn ideas into running code almost effortlessly. Going from Python to C++ feels like being super defensive and precise: every single thing is nailed down hard, and has to be just 100% lined up. It's satisfying, gratifying, solid, in a way Python code isn't.
Different projects, different goals, different timescales. Both can be good.
I spent a lot of time in Haskell and the type system is great, the issues I ran were the stuff around it: an ecosystem often missing libraries available in other languages, Haskell solutionism, and working on code bases where Haskell language experts were empowered (not necessarily the best software engineers), which caused the company to re-invent a lot of services we could have just bought.
I really do think Haskell is the world class typed functional programming language, but I'm also not convinced using typed FP is the panacea some claim it to be. Typed FP doesn't make up for bad architecture or poor dev practices, and it's still 100% possible to code your applications into a mess even with outstanding and talented devs.
So right now I like Typescript, JS, the nvim support that took 20 minutes to set up and the incredible amount of libraries and dev tools. I've only been using typescript for a month or two, so maybe my opinion will change...
What's most important also is making sure that you take away and learn from every language you work in and apply those lessons to other languages as well. Type hints on otherwise dynamic languages are a great middle ground.
What static typing offers you though is the ability to refactor large project with much greater ease.
I spend most of my time refactoring a large C# codebase (10m+ loc) while other people are actively working on the code as well.
This just wouldn’t be possible without some kind of static typing. If there is a type that is used in 500 places and you have to update all uses, static typing gives you some form of insurance that you’re not breaking much.
In large constant systems, code is read more times it is written. Documentation is usually worth the pain.
In small, transitory, or experimental systems, documentation may not be worth the additional burden.
I also think that parameter strong typing and splitting code into service helps to design better, more clear messaging between components. Having done that for some time we can train ourselves to always pay attention to it, and it will help up to build larger code bases in a maintainable way. (I think larger code bases are more efficient from “time spent” point of view)
I often see people do same mistake: they trying very hard not to repeat themselves, and the highly reusable functions become very dangerous piece to refactor. I would rather have code copied in different modules, unless it’s very general thing that never change like array sorting or date formatting
Then I started Ruby and was carried away by amazing devx even though missed static typing sometimes.
Then I discovered Typescript.
Typescript has found the perfect balance between flexibility and type checking.
Now i feel literally pain when I have to deal with static languages. It’s like im thrown to jail.
Not to mention terrible devx of the most of them.
So, probably the question should be for people with, explicitly, 20 years' professional experience, if it is to mean anything.
What did I feel during the transition: Feel like an insulted because of the under-expressiveness. Everyday I find some ideas that are natural to me and non-tech people couldn't be codified directly.
Although statically typed languages are getting better each year, I still think there's a trade-off among three camps:
1. Dynamic (Ruby, JavaScript)
2. Under-expressive static (Java, C#)
3. Expressive static (Scala, TypeScript)
I worked on a full-stack project and people have different backgrounds and have to switch from time to time, here's my impression:
- Most "underexpressive-static -> dynamic" people hate dynamic programming, because they mostly lose typing for nothing - they usually don't leverage the expressiveness because that is how they were coding all along.
- Some "dynamic -> underexpressive-static" people liked the discipline, while some hated the under-expressiveness, it depends.
- The "Expressive static" was the new thing. Everybody need to learn some part. Usually, there's a learning curve, some people like the challenge while some people hate it. That also depends on how steep the curve is (TypeScript is very smooth but not Scala).
Even though I think it's still trade-off, I can see the balance is shifting toward the "expressive static" camp:
- People get more familiar when they're exposed to the concepts. Higher-order functions like map/filter/reduce were considered exotic in the 90s, but now they're everywhere. Monadic-like programming like Promise or Optional is also popularized, they're not hard at all once "everybody else" learned that. Advanced type systems are no different.
- Language creators have more experience in choosing trade-offs that makes sense. Scala was getting a bad rap during its day, but Kotlin didn't. In other words, the creators are getting better at spending their "novelty budgets"[0] to yield optimal results.
P.S. I still couldn't wrap my head that Dart spent NO "novelty budget" whatsoever... They have zero chance against TypeScript if there was no Flutter or other Google platforms that use Dart.
https://henrikwarne.com/2014/06/22/switching-from-java-to-py...
i will say that mocking/stubbing is much easier to do with dynamic languages, but this, in theory, forces cleaner integration points in static languages (i.e. accept interfaces, return types)
recently I regretted using C for a big library as I had to tack on dynamic-like features, like huge hash tables for all it's objects and methods, so that I can dispatch dynamically. in a proper dynamic language the project would be 10x smaller and safer.
Professionally, over about 12 years, I have used Java, C++, Golang, Rust, Dart, Python, Perl, and Swift. Since learning Rust, every other type system seems slightly broken. Fixing a red-underline takes a few seconds. Debugging runtime errors takes 10-times as long. And debugging runtime errors in a long CI run can take 100-times as long. Because static types let us catch errors earlier, they speed up writing and changing code.