I posted this as a reddit comment and it was really well received, so I thought I'd post it here so it would be more linkable. A lot of people complain about "cabal hell" and ask what they can do to solve it. There are definitely things about the cabal/hackage ecosystem that can be improved, but on the whole it serves me quite well. I think a significant amount of the difficulty is a result of how fast things move in the Haskell community and how much more reusable Haskell is than other languages.
With that preface, here are my best practices that seem to make Cabal work pretty well for me in my development.
1. I make sure that I have no more than the absolute minimum number of packages installed as --global. This means that I don't use the Haskell Platform or any OS haskell packages. I install GHC directly. Some might think this casts too much of a negative light on the Haskell Platform. But everyone will agree that having multiple versions of a package installed at the same time is a significant cause of build problems. And that is exactly what the Haskell Platform does for you--it installs specific versions of packages. If you use Haskell heavily enough, you will invariably encounter a situation where you want to use a different version of a package than the one the Haskell Platform gives you.
2. Make sure ~/.cabal/bin is at the front of your path. Hopefully you already knew this, but I see this problem a lot, so it's worth mentioning for completeness.
3. Install happy and alex manually. These two packages generate binary executables that you need to have in ~/.cabal/bin. They don't get picked up automatically because they are executables and not package dependencies.
4. Make sure you have the most recent version of cabal-install. There is a lot of work going on to improve these tools. The latest version is significantly better than it used to be, so you should definitely be using it.
5. Become friends with "rm -fr ~/.ghc". This command cleans out your --user repository, which is where you should install packages if you're not using a sandbox. It sounds bad, but right now this is simply a fact of life. The Haskell ecosystem is moving so fast that packages you install today will be out of date in a few months if not weeks or days. We don't have purely functional nix-style package management yet, so removing the old ones is the pragmatic approach. Note that sandboxes accomplish effectively the same thing for you. Creating a new sandbox is the same as "rm -fr ~/.ghc" and then installing to --user, but has the benefit of not deleting everything else you had in --user.
6. If you're not working on a single project with one harmonious dependency tree, then use sandboxes for separate projects or one-off package compiles.
7. Learn to use --allow-newer. Again, things move fast in Haskell land. If a package gives you dependency errors, then try --allow-newer and see if the package will just work with newer versions of dependencies.
8. Don't be afraid to dive into other people's packages. "cabal unpack" makes it trivial to download the code for any package. From there it's often trivial to make manual changes to version bounds or even small code changes. If you make local changes to a package, then you can either install it to --user so other packages use it, or you can do "cabal sandbox add-source /path/to/project" to ensure that your other projects use the locally modified version. If you've made code changes, then help out the community by sending a pull request to the package maintainer. Edit: bergmark mentions that unpack is now "cabal get" and "cabal get -s" lets you clone the project's source repository.
9. If you can't make any progress from the build messages cabal gives you, then try building with -v3. I have encountered situations where cabal's normal dependency errors are not helpful. Using -v3 usually gives me a much better picture of what's going on and I can usually figure out the root of the problem pretty quickly.