
On Fri, Mar 22, 2013 at 1:25 AM, Edward Kmett
On Fri, Mar 22, 2013 at 12:03 AM, Ben Gamari
wrote: Roman Cheplyaka
writes: Right now Data.Typeable is proxy-agnostic. The proposal is just to export a Proxy type for convenience (alternatively, the user can define her own Proxy or use one from tagged). What exactly forces you to support both proxies in your code?
(I'm reluctant to have many proxy types scattered around mainly because of unnecessary name conflicts, but I'd like to understand your concerns too.)
It seems the options before us are,
a) Use Proxy strictly internally in Data.Typeable. In this case users will continue to use the Proxy types in tagged and elsewhere as they already happily do
b) Export Proxy from Data.Typeable in its current state, accepting that users relying on external Proxy types will need to either accept a loss of functionality, explicitly hide Typeable's Proxy, or rely on orphan instances
c) Find a way to bring Typeable's Proxy type to a level of functionality comparable to that currently available outside of base
Having tried to compile a good amount of code using Data.Typeable's Proxy, it seems clear to me that (b) is the worst of the three outcomes. There are a good number of packages which rely on the instances provided by external Proxy types. Removing these will bring great deal of pain in the short turn and pose a large maintenance burden moving forward.
Tonight I tried to implement (c) but found that this might be quite tricky without establishing some very brittle cyclic imports in base. As it stands, nearly everything in base imports Typeable somehow. Requiring Typeable to in turn import Applicative, Foldable, Traversable, and others places some very unfortunate cycles in the dependency structure. Even after an hour of hacking and 200 lines of changes, I still hadn't succeeded in getting base to build with a reasonable set of Proxy instances (although, admittedly, this might just be due to my inexperience with this sort of issue).
You don't need to make it cyclic, if the edge already exists one way in the graph just follow it back and put the instance with the class.
In light of these points, I believe that (a) is the course of least pain. Those users that need Proxy already happily rely on packages outside of base. Meanwhile base can use its own (necessarily minimal) Proxy internally without issue. This approach requires minimal changes in base and avoids unnecessary breakage of user code, all while depriving no one of current or future functionality. Admittedly, there may be a couple more Proxy types in the namespace than would otherwise exist, but this seems like a small price to pay to avoid the breakage and pain of (b) and (c) above.
I would be happy with (a) or (c).
That said, given (c) I could make Data.Proxy in tagged re-export the Data.Typeable Proxy whenever we're on a new enough GHC and I can invert all the other dependency edges. It'll be work for me, but it means that when a user goes to mix the existing tagged Proxy with code from Data.Typeable it 'just works' and there aren't two types named Proxy floating around that'll have lots of users.
It seems to me that if Proxy is going to be exported from base, it should go in its own module -- it doesn't have much to do with Typeable. One possibility would be to call the module Data.Proxy and then conditionally not export Data.Proxy from "tagged" with a recent base. Right now tagged's Data.Proxy exports a bunch of instances (Eq, Ord, Show, Read, Typeable, Data, Enum, Ix, Bounded, Functor, Applicative, Monoid, Monad, Foldable, Traversable) and four functions (asProxyTypeOf, reproxy, proxy, unproxy). Being in a separate module, all the instances could be added pretty easily. "proxy" and "unproxy" mention Tagged, so they can reasonably be moved into Data.Tagged. The other two -- asProxyTypeOf :: a -> Proxy a -> a; reproxy :: Proxy a -> Proxy b -- look like reasonable Proxy functions that could go into base (maybe one or both of them could even be made proxy-polymorphic). Alternatively, any other module could define a Proxy type with all the relevant instances (pushing some instances out to the classes' modules as mentioned) and then tagged's Data.Proxy could re-export it and define all the helper functions. That module could be Data.Typeable but I don't think it belongs there (but it doesn't matter that much). At any rate I agree that if this moves into base, it shouldn't lose any functionality (non-orphan instances are functionality), and that a duplicate Proxy is worse than not having one in base at all. After all, base doesn't need to *use* it. Shachaf