I have yet to experience a dynamically typed code base in which missing types was helpful and in which you weren't required to trace multiple layers of functions to understand the argument and return types of the function you care about, even with pattern matching.
Your last paragraph is accurate regarding the tradeoffs of a statically typed language. However, it leaves off the tradeoffs of a dynamically typed language. In a statically typed language with type inference, in the main portion of the system, statically inferred types are a massive benefit and save a lot of heartache. When you call a function, you know its types. Yes, at the boundaries of a program such as I/O and sometimes message passing, things get sticky. Dynamically typed languages help at the boundaries because they loosen things up enough to be helpful. However, they throw out all the benefits in the parts of the system away from the boundaries. I view typespecs as a necessary tradeoff to accommodate static types missing from those parts of the system. In such parts in a dynamically typed language, one basically is coding like you would in a statically inferred typed language and is not taking advantage of dynamic types, so typespecs should be there. I'd argue that abuse of dynamic types away from boundaries should be considered very seriously, same as one would do with macros.
> 90% of the time the types are simply an extension of the database and enforced strictly at the database layer while everything else is just data pass through anyway (in Ruby, Python, PHP, etc). You understand the database, you understand the application data.
Case in point. In a large system, why do I or others need to understand the data at the database layer to develop something many layers above or away from that? There is almost always a transformation from a storage data structure to the in-application and domain data structures. Databases are for storage and state retention, not application logic and data structures. Your 90% of the time applies at the database interaction layer, but it is not the majority of the code.
> I really think it's just one of those areas where people come at problems differently. Some people love it and swear by it. For those people, I think the lack of static typing causes a real gap in their entire approach to programming which translates to frustration and seeing a lot of "this wouldn't have been a problem with static typing..." experiences.
You seem to put it on developers coming to dynamically typed languages from statically typed languages having a "gap" in their approach. When wanting to call a function, I do not see it as a gap in one's skills or experience in not wanting to have read the implementation of that function and the functions it calls. When calling a function, I want to know its name, its argument names and types, the return types, and documentation. I don't see why it's a gap in one's approach to want that. It's almost the first thing you learn when learning software development. I think dynamically typed developers are just used to the overhead of reading function definitions to understand how to call things or they are the ones who wrote or have significantly used the code base.