So speaking as someone who has been on both sides of that fence (and, honestly, prefers static or optional typing; Dialyzer for Erlang is probably my favorite approach there), I think a large part of that comes down to how types are used.

In a very OO language, where you're encouraged to create a complex type for every function/method contract (i.e., I have a type of RoomMeasurement, that internally contains a list of Measurement interfaces, each of which is in fact implemented as a MeterMeasurement, which wraps a double), static typing is very, very necessary, because it's not at all obvious what a function takes. And you need thorough API documentation because how another developer has chosen to represent things is not obvious (that is, you are trying to interoperate with a library that doesn't understand your RoomMeasurement, but does work with just a list of measurements, but they have to be in imperial, not metric, and how do you get the list of measurements from your RoomMeasurement, and convert them? Do you have to write a function, is there one already, does it take the RoomMeasurement, does it take the list of MeterMeasurement, does it just take a single MeterMeasurement? Etc)

When sticking with simple types, though, it becomes a lot easier to reason about, and you can get away with just comments, or very slightly more complicated types. A list of measurements is just a list of ints...but maybe dropped into a tuple where the first arg is the type (i.e., roomMeasurements = {meter, [4.22, 5.7, 3.1]} ). And all you have to find/write (and since it's just data it doesn't matter which because there's no hidden stuff that needs tweaking) a meterToFoot function, and apply it as a map. I.e. (pseudocode), ->

feet = {foot, map(roomMeasurements[1], meterToFoot)}

Now, is that perfect? No; even if you as a developer choose to apply type information, another developer can choose to ignore it. But I think that's rarer; more common is when people don't think to supply type information at all, and pass around just (per the example), an array of doubles. You can do that in a statically typed language too, though.

I think the key difference is that people coming from a statically typed, OO language, to a dynamically typed FP language, can either end up creating the same complex data types (which are a nightmare to deal with even with static typing, but doubly so without), or they see all the examples, embrace the simpler data structures...and then don't actually supply the necessary typing information.

The reason, then, that I think static typing -is- good, is because it makes it harder for me to ignore/forget to handle the typing I have provided. That is, I may have a 'metricToImperial' function, that takes in a type tagged array of doubles, and a desired type, and determines and applies the appropriate function. But I can still forget to include a necessary conversion in the resulting case statement ('whoops, I called it with a lb to g conversion over here, and I forgot to implement that one'). It's times like that I really like optional/inferred typing; many places I don't need to check for typing, because it's obvious, both to the developer, and to the compiler if it can infer types...but I can still make mistakes, per that. Of course, that brings me to unit testing...