That is a valid point. It makes sense to tie the upper bounds of dependencies to how you use a dependency. It’s useful when you’re setting the bounds as the maintainer of some package. After all, if a new version of the dependency is released, the version bound starts complaining, meaning that you need to check and fix your package.
The other perspective, though, is the end users of your library. The dependency version bound also starts complaining to them, and they will have to deal with it. This is often the situation I find myself in. Some packages have super strict version bounds, causing them to break every other nixpkgs update we do.
When I update dependencies, I often see package bound errors. It might sound like heresy, but my very first go-to then is the pkgs.haskell.lib.doJailbreak
function. This function simply erases the version bounds of the package. This sounds reckless, but 9 times out of 10, the package builds and even functions perfectly fine. The tenth time I get a clear compiler error.
Intuitively I feel that the tightness of version bounds is a tradeoff: too loose, and you’re falsely promising to work with dependency updates forever. Too tight, and your package cries incompatibility very often and often wrongly.
It also depends on how closely your dependencies actually follow PVP. Any mistake or misinterpretation makes everything just so much more difficult.