Proxy and new-typeable
Shachaf Ben-Kiki
shachaf at gmail.com
Fri Mar 22 07:55:19 CET 2013
On Fri, Mar 22, 2013 at 1:25 AM, Edward Kmett <ekmett at gmail.com> wrote:
> On Fri, Mar 22, 2013 at 12:03 AM, Ben Gamari <bgamari.foss at gmail.com> wrote:
>>
>> Roman Cheplyaka <roma at ro-che.info> 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
More information about the Libraries
mailing list