Backward-compatible role annotations
ml at isaac.cedarswampstudios.org
Wed Apr 9 02:28:49 UTC 2014
I've been wondering whether there are any cases where the ability to
coerce a parameter should not be exported (as for Set) but where the
implementation would like to use some coercions (for example, if it
internally wanted to coerce the contained type to a specific newtype, to
change some of the contained type's instances while being able to
guarantee that the invariant-related instances stayed safe).
This would call for coercibility to be something you can import and
export. What if, in the long term, there were a flag
ExplicitRoleExports (probably rarely used):
- with ExplicitRoleExports on, role signatures would be listed in the
export list and must be at least as restrictive as the roles visible
within the module. Exported types without role signatures in the export
list would default to being exported as all-nominal.
- with ExplicitRoleExports off, any types the modules export would be
exported with the same roles visible inside the module.
As is currently, the default role at a type's definition site would
allow as many coercions as is segfault-safe.
P.S. I think this particular proposal likely has issues. If you import
(T :: * -> * -> *) as "nominal, representational" and from elsewhere as
"representational, nominal", do those roles combine somehow? What about
contravariant [or is it covariant] roles as in (T :: (* -> *) -> *)? I
haven't thought these through. Also I haven't thought of any concrete
use cases for export-controlled roles; perhaps there aren't any.
On 03/31/2014 02:12 PM, Dominique Devriese wrote:
> Right, but I was thinking about the debate between
> "nominal/non-parametric-by-default" or
> "representational/parametric-by-default" for parameters of data types
> that aren't forced to nominal from inspecting the datatype
> implementation. As I understand it, representational-by-default
> (currently used) may leave libraries that don't add role annotations
> open for abuse, but won't unnecessarily break library users' code and
> nominal-by-default prevents all abuse but may unnecessarily break code
> that uses libraries that have not added proper role annotations.
> What I was wondering about is if the dilemma could be solved by
> choosing nominal-by-default in the long term for the role inference
> (so that library writers cannot accidentally leave abstraction holes
> open by forgetting to add role annotations) and use them in the
> long-term-supported SafeNewtypeDeriving extension, but provide a
> deprecated not-quite-as-safe GND extension for helping out users of
> libraries that have not yet added role annotations. I would fancy that
> this not-quite-as-safe GND could use unsafeCoerce wherever the safe
> one would give an error about annotated roles.
> 2014-03-31 17:05 GMT+02:00 Richard Eisenberg <eir at cis.upenn.edu>:
>> Hi Dominique,
>> When implementing roles, I was indeed worried about the problem you're addressing: that code that previously worked with GND now won't. However, it turns out that few people have really complained about this. IIRC, in all of Hackage, only 3 packages needed to be changed because of this. If there were a larger impact to the GND breakage, I think your suggestion would be a good one.
>> The problem I'm adressing in this thread is different: that library authors have been given a new, not-backward-compatible way of preventing abuses of their datatypes, and no proposal I have seen really addresses all of the problems here. I'm hoping my no-role-annots package might be helpful, but it doesn't fully resolve the issues.
>> On Mar 31, 2014, at 2:51 AM, Dominique Devriese <dominique.devriese at cs.kuleuven.be> wrote:
>>> (re-posting because I first used an address that is not subscribed to the lists)
>>> I've been wondering about the following: it seems like the main
>>> problem in this situation is that the GeneralizedNewtypeDeriving
>>> extension changed meaning from "just coerce everything while deriving"
>>> to "only coerce stuff if it's allowed by the relevant role
>>> annotations". Would it not be an alternative solution to split up the
>>> GND extension into
>>> * a backwards-compatible one (called GeneralizedNewtypeDeriving for
>>> backwards compatibility ;)) that ignores role annotations (as before)
>>> and uses unsafeCoerce whereever necessary
>>> * a safe one (called e.g. SafeNewtypeDeriving) that respects role annotations
>>> The first one could then be deprecated and removed in a release or
>>> two. That might give library maintainers time to move their packages
>>> to SafeNewtypeDeriving when they have tested that everything works...
>>> P.S.: The above is based on a limited understanding of the problem, so
>>> I'm sorry if it misses some aspect of the problem...
>>> 2014-03-31 2:14 GMT+02:00 Richard Eisenberg <eir at cis.upenn.edu>:
>>>> I spent some time thinking about what, precisely, can be done here to make
>>>> folks happier. (See the thread beginning here:
>>>> http://www.haskell.org/pipermail/libraries/2014-March/022321.html) And, the
>>>> answer seemed to all be in the concrete syntax. The only logical alternative
>>>> (that I could think of) to having roles is to disallow GND, and I don't
>>>> think anyone is proposing that. And, it is impossible to infer an author's
>>>> desired roles for a datatype. The heuristics mentioned here all seem far too
>>>> fragile and hard to predict to become a lasting feature of GHC (in my
>>>> opinion). What's left? Concrete syntax.
>>>> So, I have written and uploaded no-role-annots-1.0, a backward-compatible
>>>> alternative to role annotations -- no CPP required. It's not as principled
>>>> as proper role annotations, but it should do the job for most users.
>>>> Here are two examples:
>>>> 1. Datatypes:
>>>>> import Language.Haskell.RoleAnnots
>>>>> data Map k v =
>>>>> (Nominal k, Representational v) => MkMap [(k,v)]
>>>> The constraints (which need be put on only one data constructor, if there
>>>> are many) will get the built-in role inference mechanism to do what the user
>>>> requests. In this example, the `Representational v` is actually redundant,
>>>> but causes no harm. Because these classes have universal instances
>>>> ("instance Nominal a") and have no methods, they should have no run-time
>>>> significance. The only downside I can see is that the code above needs
>>>> -XGADTs or -XExistentialQuantification to work, though it is neither a GADT
>>>> nor has existentials. (Pattern-matching on such a definition needs no
>>>> 2. Newtypes:
>>>> Newtype constructors cannot be constrained, unfortunately. So, we have to
>>>> resort to Template Haskell:
>>>>> import Language.Haskell.RoleAnnots
>>>>> roleAnnot [NominalR, RepresentationalR]
>>>>> [d| newtype Map k v = MkMap [(k, v)] |]
>>>> This is clearly worse, but I was able to come up with no other solution that
>>>> worked for newtypes. Note that, in the example, I used the fact that
>>>> Template Haskell interprets a bare top-level expression as a Template
>>>> Haskell splice. We could also wrap that line in $( ... ) to be more explicit
>>>> about the use of TH. Also problematic here is that the code above requires
>>>> -XRoleAnnotations in GHC 7.8. To get this extension enabled without using
>>>> CPP, put it in a condition chunk in your .cabal file, like this:
>>>>> if impl(ghc >= 7.8)
>>>>> default-extensions: RoleAnnotations
>>>> I hope this is helpful to everyone. Please feel free to post issues/pull
>>>> requests to my github repo at github.com/goldfirere/no-role-annots.
>>>> ghc-devs mailing list
>>>> ghc-devs at haskell.org
>>> Libraries mailing list
>>> Libraries at haskell.org
> ghc-devs mailing list
> ghc-devs at haskell.org
More information about the ghc-devs