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 :(