One reason for this is the tight coupling in developers' minds between what I'd call "types" and "representations" (these terms are very overloaded, so others may use them in different ways).
Just because, say, a function name and a string are represented in memory the same way, that doesn't mean they are the same type; in particular there are many strings which aren't function names, and there are many operations (e.g. append) which make sense for strings but not for function names.
I can think of two things to blame for this:
- Languages which make it complicated, verbose and/or inefficient to introduce new, incompatible names for existing representations (i.e. nominal typing). For example, having to declare a new class in a new file with a private field, a constructor method and a getter method; that's a lot of boilerplate, and those method calls will incur a runtime cost. Haskell's `newtype` is pretty good in comparison, e.g. saying `newtype FunctionName = FN String` lets me use `FunctionName` in type signatures, I'll get a type error if I try to use a `String` as a `FunctionName` or vice versa, and I can construct and destruct a `FunctionName` using `FN` (e.g. `let x = FN "foo"`)
- Developers only coding for the happy path. When writing a test suite, it's important to test that bad outcomes are prevented, as well as just testing known-safe inputs. The same should apply to types: types should be used such that meaningless expressions are ill-typed, as well as just allowing known-good expressions to be well-typed.
Unfortunately these sorts of considerations tend to get derailed by similar-but-unrelated issues, e.g. 'YAGN a `FunctionName` interface because there's only one implementation'.
The end result of all this is Web applications concatenating together a bunch of "strings" which are actually HTML, plaintext, SQL, user input and URL parameters :(