Your data structures can be parametrized, no? I mean nobody creates a ListOfFoos or HashMapOfBars, it's List[Foo] or HashMap[Bar] where Foo and Bar can be replaced with anything and methods defined on List and HashMap would still work.
Can you perhaps give a more concrete example?
Note that inheritance does not solve the problem of overly-specialized functions. The statically-typed language could make Player inherit from the built-in HashMap type, but the functions that require Player will still not work with anything else.
I think you are equating statically typed to Java. There are plenty statically typed languages which will take care of this, while still avoiding many runtime errors. Heck, even C++ templates will do this for you, although C++ with concepts would be a lot better, sans the compile time. Typeclasses also address the same problem: to signal compile error early.
What would you say the latter buys you?
I'm not saying I don't see any advantages, particularly compared to practices in some specific statically typed languages, but I think that clarifying this would be useful to everyone in this discussion.
struct Point { int X; int Y; ... }
struct Size { int Width; int Height; ... }
In either case the data structure is (int, int). But they're typed differently: one is typed as Point and the other as Size. This makes it very annoying to write functions that are compatible with both. Let's say we write AddThenSquare(Point A, Point B) {
return new Point((A.X + B.X)*(A.X + B*X),
(A.Y + B.Y)*(A.Y + B.Y));
}
To make Size compatible with that function you need to either refactor your code so that both Point and Size adhere to the same interface, or alternatively implement conversion functions. The latter option is what C#'s .NET framework opts for where Point and Size are taken as parameters to each others constructors. But it doesn't scale. What happens when you use a third party math library with its own point implementation? Your number of conversion functions grow by n*(n-1) where n is the number of classes that need to be converted between. To be fair I think C# has technical reasons for not having an interface between Point and Size (structs don't play nicely with interfaces) but even if it was included it would still be a royal pain making sure all other math libraries adhered to the same one.In practice it's rare anyone will have the foresight to put interfaces everywhere they're needed, and it's even rarer that programmers will use the same interfaces across libraries. It begins to sound like the "specialization within functions that inhibits and penalizes casual cooperation" that Alan Perlis was talking about.
At some point you start to wonder why the compiler cares at all what the name of the data is if the underlying information is the same. You could have dodged the headache altogether if you were in Python where a point is a tuple (x, y).
Still I'm not convinced static typing is to blame... it's more that the kind of static typing employed by Java, C++, C# is especially rigid.
I can't think of a meaningful function that should be able to support both Point's and Size's. Except for prettyprinting or serialization maybe, but that can be handled in statically typed languages, no?
But admittedly conversion between Points and Sizes is not a pain point for most people. It's much worse when dealing with types that have the exact same purpose, just from different libraries.