I've written a not-so-small-anymore Lisp implementation, and wouldn't call it cohesive. Even a one-person design won't be cohesive without a laser focus on cohesivity, which guides every design decision, and which sacrifices compatibility with anything existing, also.
> Why can’t equality operators be extended to user-defined types?
That's a loaded question where a bad answer is worse than no answer.
I have taken a crack at it by inventing a concept called "equality substitution".
Equality for a new user-defined type is defined using a one-argument method, which takes an instance of that type itself. When an object of that type is compared with something using equal, then that method is called, and the return value is used instead; that is then iterated on. If an object has no such method, then its identity is used: the opposite argument must be that same object.
Thus, custom equality works by substituting some ordinary object: hence, "equality substitution".
For instance some employee object may have an equal function which returns the employee ID (an integer). Then employee object whose ID is 42, is equal to the integer 42, and therefore any other employee object whose ID is 42, or any other non-employee object also which returns 42.
To make it safer, the function could return a cons instead like (employee . 42), where the type symbol appears in the car. That cons is then equal only to another cons which has employee in car and 42 in cdr.
Equality substitution neatly solves the problem of hashing also. The hash able implementation uses the equality substitute of an object as the key: that's the value it hashes, and that's the value it compares.
The programmer need not define a custom hashing function, which is an error-prone, low-level task that we would not like to foist upon application programmers.
Now, not everyone will find it appealing that (equal non-cons-object '(employee . 42)) can yield true. They might say, if you believe that no answer is better than a bad answer, why on Earth did you do such a thing?
> Why pathnames?
Portability of code among very diverse historic operating systems, installations of which are no longer in deployment.
Pathnames in CL may be a pain in the but, but they are arguably a better abstraction than raw strings.
Mainly the thing that is wrong with CL pathnames is that the spec leaves certain behaviors implementation-defined in such a way that two people writing a CL implementation on POSIX, say, will end up with different behaviors. There is more than one way that POSIX path strings can correspond to pathnames objects. Oh, like for instance in foo.tar.gz, is "gz" the :type? Or is "tar.gz" the :type? That creates ironic situations whereby we can't port a Lisp program to the one single plaform using two implementations of the language, such that the behavior is identical!
A new revision of ANSI CL could solve this: it could specify that on Microsoft Windows, the correspondence between pathname objects and strings is such and such, and on POSIX such and such, and so forth.