Why is this desirable vs. implementing those types in the language itself?
I think we've gone down a weird path where implementing things "in userspace" is seen as an inherent good -- why?
That's a very good question.
1. It's simpler. In D, we transitioned from a builtin complex type to a library type. It was a happy experience. The simpler the compiler, the easier it is to deal with.
2. Most any programmer can create a library type. Relatively few can modify a compiler to add a new type.
3. It takes the pressure off the compiler team to develop more arithmetic types.
4. Users can add arithmetic types without needing anybody's consent, and can do it right now.
5. It tests and verifies the metaprogramming abilities of the language.
There are always going to be more numeric-like types (int, float, complex, bigint, rational, symbolic-comp types etc.). Numeric vectors that act like numbers are useful (see: numpy), and trying to stick those all into the language is far harder than allowing extensibility.
But at around the end it asks this (paraphrasing): Should a language have complex numbers implemented natively? Should it have numbers module n implemented natively? What about intervals, or rational numbers?
I also dislike the ridiculous overloading of cryptic symbols, but neither extreme is good. Not allowing overloading will give you BigDecimal.of(3).add(BigDecimal.ONE).divide… , while allowing it unrestricted will give you things like msg #!! something. But perhaps a sane middle ground is to allow only basic operators (+,-,*,/) to be overloadable.
I used to agree with you, because of the abuse of operator overloading like iostreams and making DSLs with it. D does support it, but in a way that discourages non-arithmetic uses (such as it won't allow < <= >= > to be overloaded separately).
Operator overloading allows for things like complex numbers, arbitrary precision numeric types, etc., to be done with a library module.