Newtype wrappers
Simon Peyton-Jones
simonpj at microsoft.com
Fri Aug 2 07:45:12 CEST 2013
Joachim
I was talking to Stepanie about newtype wrappers, and realised that
a) there are two very different ways to use type classes to
implement the feature, one good and one bad
b) the one I thought we were using is the Bad One
c) the one on the wiki page is sketched in far too little detail
but is the Good One
http://ghc.haskell.org/trac/ghc/wiki/NewtypeWrappers
Also, there's an interaction with "roles" which Richard is on the point of committing.
Below are some detailed notes.
Before too long, could you refresh the wiki page to:
* demote rejected ideas
* explain the accepted plan in more detail
* discuss implementation aspects
Thanks
Simon
Bad version
~~~~~~~~~~~
class NT a b
rep :: NT a b => a -> b
wrap :: NT a b => b -> a
Then for each newtype
newtype Age = Int
we generate
axiom axAge :: Age ~ Int
instance NT Age Int
and for data types
data (a,b) = (a,b)
we generate
instance (NT a a', NT b b') => NT (a,b) (a',b')
Internally (NT a b) is implemented as a simple boxing of a
representational equality:
data NT a b = MkNT (a ~#R b)
There are many problems here
- It's tiresome for the programmer to remember whether
to use rep or wrap
- Many levels of newtype would require repeated application
of rep (or wrap). But if I say (rep (rep x)) I immediately
get ambiguity, unless I also supply a type sig for each
intermediate level.
Good version (this may be what you are doing already)
~~~~~~~~~~~~
class NT a b
ntCast :: NT a b => a -> b
Notice only one ntCast, which will work both ways.
Then for each newtype
newtype Age = Int
we generate
axiom axAge :: Age ~ Int
instance NT Int b => NT Age b
instance NT a Int => NT b Age
Notice *two* instances
For data types, same as before
data (a,b) = (a,b)
we generate
instance (NT a a', NT b b') => NT (a,b) (a',b')
And we have instance NT a a.
Now try this
f :: (Age, Bool, Int) -> (Int, Bool, Age)
f x = ntCast x
>From the ntCast we get the wanted constraint
NT (Age,Bool,Int) (Int,Bool,Age)
Then we simplify as follows
NT (Age,Bool,Int) (Int,Bool,Age)
---> by instance decl for (,,)
NT Age Int, NT Bool Bool, NT Int Age
---> by instance NT a a, solves NT Bool Bool
NT Age Int, NT Int Age
---> by instance NT Int a => NT Age a
NT Int Int, NT Int Age
---> by instance NT a a, solves NT Int Int
NT Int Age
...and so on...
In effect, we normalise both arguments of the NT independently
until they match (are equal, or have an outermost list). This
will even do a good job of
newtype Moo = MkMoo Age
f :: Moo -> Age
f x = ntCast x
Try it.. it'll normalise the (NT Moo Age) to (NT Int Int), so the
coercion we ultimately build wil take x down to Int, then back up
to Age. But that's fine... the coercion optimiser will remove the
intermediate steps.
This has lots of advantages:
- No need to remember directions; ntCast does the job in either
direction or both
- No need for multiple levels of unwrapping... and hence no
ambiguity for the intermediate steps.
A note about roles
~~~~~~~~~~~~~~~~~~
We are very keen that if we declare
data Map key val = [(key,val)]
where the keys are kept ordered, then we do NOT want to allow
a cast from (Map Int val) to (Map Age val)
We were thinking about exercising this control by saying, in
a standalone deriving clause
deriving NT v1 v2 => NT (Map k v1) (Map k v2)
But another, and more robust, way to express the fact that we
should not allow you to cast (Map Int val) to (Map Age val) is
to give Map the rules
p Map key at Nominal val at Representational
in Richard's new system (about to be committed). This gives
Map's first argument a more restrictive role than role inference
would give it.
And for good reason! If we had
class C k where
foo :: Map k Int -> Bool
then we would NOT want to allow
newtype Age = MkAge Int deriving( C )
And the thing that now prevents us doing that 'deriving( C )' is
precisely the more restrictive role on Map.
So, our proposal is: for every
data T a = ... deriving( NT )
we get the lifting instance
instance NT a b => NT (T a) (T b)
whose shape is controlled by T's argument roles.
We probably want to be able to say -XAutoDeriveNT, like -XAutoDeriveTypeable.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/ghc-devs/attachments/20130802/c33bae6d/attachment.htm>
More information about the ghc-devs
mailing list