
Devs, Shayan is working away on "Trees that grow"... do keep it on your radar: To: ghc-devs Sent: 25 May 2017 23:49 Do take a look at this: * We propose to re-engineer HsSyn itself. This will touch a lot of code. * But it's very neat, and will bring big long-term advantages * And we can do it a bit at a time The wiki page https://ghc.haskell.org/trac/ghc/wiki/ImplementingTreesThatGrow has the details. It's entirely an internal change, not a change to GHC's specification, so it's independent of the GHC proposals process. But I'd value the opinion of other GHC devs Meanwhile I have a question. When pretty-printing HsSyn we often have a situation like this: data Pass = Parsed | Renamed | Typechecked data HsExpr (p :: Pass) = HsVar (IdP p) | .... type famliy IdP p where IdP Parsed = RdrName IdP Renamed = Name IdP Typechecked = Id instance (Outputable (IdP p)) => Outputable (HsExpr p) where ppr (HsVar v) = ppr v The (ppr v) requires (Outputable (IdP p)), hence the context. Moreover, and more seriously, there are things we just can't pretty-print right now. For example, HsExpr has this data constructor: data HsExpr p = ... | OpApp (LHsExpr p) (LHsExpr p) (PostRn p Fixity) (LHsExpr p) To pretty-print the third argument, we'd need to add instance (Outputable (IdP p), Outputable (PostRn p Fixity)) -- New => Outputable (HsExpr p) where ppr (HsVar v) = ppr v and that gets onerous. So today we never print these annotations, to avoid bloating the instance contexts, which can be painful. It bit me yesterday. We have bitten that bullet for the Data class: look at HsExtension.DataId, which abbreviates the long list of dictionaries: type DataId p = ( Data p , ForallX Data p , Data (NameOrRdrName (IdP p)) , Data (IdP p) , Data (PostRn p (IdP p)) , Data (PostRn p (Located Name)) , Data (PostRn p Bool) , Data (PostRn p Fixity) ,..and nine more... ) Let me note in passing that [wiki:QuantifiedContextshttps://ghc.haskell.org/trac/ghc/wiki/QuantifiedContexts] would make this somewhat shorter type DataId p = ( Data p , ForallX Data p , Data (NameOrRdrName (IdP p)) , Data (IdP p) , forall t. Data t => Data (PostRn p t)) But we still need one item in this list for each type function, and I am worried about how this scales to the [wiki:ImplementingTreesThatGrowhttps://ghc.haskell.org/trac/ghc/wiki/ImplementingTreesThatGrow] story, when we have a type function for each data constructor -- and there are a lot of data constructors. So I have four questions 1. I think we should probably use a superclass instead of a type synonym class (Data p, ForallX Data p, ....) => DataId p where {} Why? Only one argument to pass, and to pass on to successive calls. I see no downside. 1. Shall we treat Outputable like Data? (I.e. make an abbreviation for a long list of Outputable instances and use it everywhere) 2. I thought of another way to do it (pass a token); see below 1. Are there any other ways? Token passing idea. Perhaps instead of passing lots of functions, we pass a singleton token that encodes the pass, like this: instance (PassC p) => Outputable (HsExpr p) where ppr (HsVar v) = case getPass :: IsPass p of IsParsed -> ppr v IsRenamed -> ppr v IsTypechecked -> ppr v The three ppr's are at different types, of course; that's the point. The infrastructure is something like class PassC p where getPass :: IsPass p data IsPass p where IsParsed :: IsPass Parsed IsRenamed :: IsParsed Renamed IsTypechecked :: IsParsed Typechecked instance PassC Parsed where getPass = IsParsed ...etc... Now we could sweep away all those OutputableX classes, replacing them with dynamic tests on the singletons IsParsed etc. This would have advantages: - Probably faster: there's a dynamic test, but many fewer dictionary arguments and higher-order function dispatch - Only one dictionary to pass; programming is easier. The big downside is that it's not extensible: it works only because we know the three cases. But the "Trees that Grow" story really doesn't scale well to pretty-printing: so maybe we should just give up on that?