
Hi Yhc did a few of these things, and our experiences were:
1a. Use do notation where possible instead of `thenXX`.
If it is that simple, yes. Certainly Yhc uses "super-monads" which made this a bit painful. You should probably step this by getting it to the stage where thenXX = >>=, then only flipping to do notation once the things are already monads.
1b. Even better, use Applicative where possible. For example
ds_type (HsFunTy ty1 ty2) = dsHsType ty1 `thenM` \ tau_ty1 -> dsHsType ty2 `thenM` \ tau_ty2 -> returnM (mkFunTy tau_ty1 tau_ty2)
Could be rewritten as
ds_type (HsFunTy ty1 ty2) = mkFunTy <$> dsHsType ty1 <*> dsHsType ty2
I still don't particularly like applicative, it is a bit confusing to me. If you moved to using the Uniplate library, you could then write: descendM dsHsType Shoter, more concise, and probably works for more cases than just HsFunTy - eliminating even more code. I suspect moving to Uniplate would give GHC massive benefits.
4. A more radical change would be introducing hierarchical modules. This could be just a matter of renaming the directories to start with an upper case character and changing the import declarations. This gives module names like "Typecheck.TcGadt". The tc is redundant here, so perhaps it should be renamed to "Typecheck.Gadt" or "Typecheck.GADT". Perhaps even better would be "GHC.Typecheck.GADT", this way some modules can be exposed as part of the GHC api.
We did this in Yhc, and it was really useful just to group files into the same directory. Thanks Neil