Rich Hickey's talks can be very insightful in this regard. He surely convinced me :)
Rich Hickey's talks can be very insightful in this regard. He surely convinced me :)
On the other extreme, you have Rust and Haskell type systems where you spend gratuitous time on pacifying the type checker for negligible quality gains (arguably negative gains).
I always felt Go hit the sweet spot.
It's easy to confuse type systems with correctness or clarity of thought. I've fallen victim to this myself – "oh this didn't map well to Rust's type system, I must be thinking wrong", kinda. It tickles this ocd nerve and I lose focus on the problem I'm trying to solve, often without realizing that I'm just wasting time.
I also like Go for these reasons. It's dumb and predictable, which let's me focus on the problem I'm solving. My only annoyance is really verbosity and small amounts of boilerplate.
Unfortunately, this nuance is missing from the discussion with people mostly talking about software as though it’s all mission critical / infrequently updated / rapid iteration doesn’t matter which is exactly incorrect for the general case.
When programming in the large, type systems are flat out an unmitigated win. The more you can confidently assert about your program before run time, the more time and money your team will save. If you are programming a large system and not using strong static typing, optimally with higher-level features like parametric types, then you are doing it wrong.
In the early stages of developing something complex you often want to play fast and loose with the type system - get some code running, and some tests set up, and go from there. But later on you want to refine it and start catching some more errors statically.
The pendulum has swung really far to static typing - and I get why. But if it swings back I hope we don't forget about gradual typing.
The poster child of gradual typing has to be TypeScript, and it's been a wild success. TypeScript allows gradual implementation to any JavaScript project and there's almost a million small different gradients you can use. From simply using a js file, to noImplicitAny, to simple types, to using complicated built-in types (eg Partials).
With TypeScript, you never lose the speed of dynamic typing. The whole point of gradual typing is you can jump between more and less types, so at no point are you restricted from reverting to full dynamic typing. And it is true, you can't rely on all types being checked, but the point is you add it to the important/stable bits. And having your largely stable important bits being typed while your more experimental unstable code can be dynamic (while still getting the benefit of consuming the stable types) is a nice place to be.
As I said, it is what you make of it. If you spend all your time typing the unstable bits and leave the stable core bits untyped, well, you've just wasted your time and would get the worst of both worlds.
I believe Haskell has them, is that where you've used them?
I mainly use Swift these days but I haven't had a need for them yet so was asking out of curiosity.
I'm not kidding. Go is at a very nice verbosity level as far as reading code goes, but writing it is indeed tedious occasionally. However, in practice, Copilot is able to handle those bits extremely well, giving us the best of both worlds.
(Edit: This is a serious question; I recently semi-seriously commented that maybe solutions like Copilot are particularly well suited to solve this problem with Go, so I'm curious how well it works in practice.)
I'm mostly talking about boilerplate like writing `if err != nil` with a nice wrapping message every other line. Copilot will just write 2-3 lines at a time. That's imo the kind of boilerplate that might often be annoying to write.
I'm not talking about generating multiple copies of the same function nor anything like that. That would be better suited to deterministic generation or copy+paste. Copilot doesn't help you here.
My bigger annoyance is when I want to write non-trivial functionality that works for different data types.
I'm not aware of anything else that writes good wrapping error messages automatically though.
Your use-case is precisely what you'd be using generics / standard codegen for.
Each error wrap has a situation-dependent wrapping message, different for each wrapped error in the function.
Copilot is great at writing the whole `if err != nil`, including context-specific wrapping messages. It's also great at writing your code in many other situations where the intent is clear and simple, but it just takes a few lines to write in Go.
I'm not sure if you've ever used Copilot yourself, but it's basically autocomplete on steroids.