I liken it to David Deutsch’s comments on good systems of government in his book The Beginning of Infinity where he advised that the trait you should use to evaluate a system of government is not whether it produces good policies, but rather how easy it is to remove bad policies.
Languages don’t cause people to invent better designs for mapping between the real world and software abstractions. They can provide tools to help, but they don’t cause the design.
But statically typed languages do create boilerplate and sunk cost fallacies leading to living with bad designs and accepting limitations that have to be coded around.
Dynamic typing compares favorably in this regard: it is very easy to rip things out or treat a function as if it implicitly handles multiple dispatch (because you can specialize on runtime types with no overhead and no enforcement on type signatures or type bounds), and quickly get feedback on whether a design will be a good idea, or what things would look like ripping out some bad design.
I think exactly what you describe is the hyped up promise that static typing, especially in functional languages, fails to actually deliver in practice. You can still write production code that way, just incurring costs of maintenance of more code & boilerplate without the supposed offsetting benefits of catching more bugs, reducing runtime errors, or communicating design more smoothly in the type system, except in isolated, small parochial cases.
Somewhere along the spectrum of increasing cost to business of runtime errors the needle switches in favor of static tyoes. This is more true when you ship applications to folks who dont necessarily know or care about the internals. Throwing runtime errors is just a bad form in those cases.
When the code is going to be deployed on infrastructure you control, there is a lot more leeway to absorb runtime errors. The choice depends on how costly the runtime errors are and how costly are the fixes. Time being part of the cost.
The marketing pitch for that is always something like QuickCheck in Haskell, where e.g. reversing an array should be its own inverse function and you can auto-verify this like it is a law across a bunch of cases.
The problem is in real life unit tests, nothing has any laws like this, and it’s just a bunch of bizarre case-specific business logic and reporting code. The concept of a corner cass is a semantic one, and the definition of what inputs are possible to a given function will change and have constraints from the outside world that not even the most expressive statically typed language will easily let you encode into the type system.
Combine it with the fact that your colleagues have variability in their skills too, and often won’t make good choices with type system abstractions to represent business logic, and then all that costly extra boilerplate code for specifying types, creating your own business-logic-specific ADTs, adding privacy modifiers, templated or type classes implementations...
...it just becomes a big pile of garbage liabilities for what turns out to seriously be no benefits over dynamic typing.
Even in the static typing case, you’ll end up with tons of runtime errors causing you to frequently revisit assumptions in the unit tests. You’ll just have a harder time refactoring large pieces of code that are wedded to particular type designs and you’ll have to sit and wait on the compiler to try every change (this can be hugely bad when the system has components needed for rapid prototyping, interactive data analysis, or other real-time uses).
I’ve really seen a lot of corners of this debate play out in practice, and static typing beyond extremely simple native types and structs (basically C style), really offers nothing while being a huge productivity drain. The claims that it actually helps productivity because the compiler catches errors and forces more correctness just turns out to be false in real code bases. You get just as many weird runtime errors and just have a harder time debugging or rapidly experimenting with changes.