... | ... | @@ -6,15 +6,21 @@ Deriving strategies grant users finer-grained control over how instances may be |
|
|
## Motivation
|
|
|
|
|
|
|
|
|
|
|
|
GHC Trac [\#10598](https://gitlab.haskell.org//ghc/ghc/issues/10598) revealed a limitation of GHC's current instance deriving mechanism. Consider the following program which uses both `DeriveAnyClass` and `GeneralizedNewtypeDeriving`:
|
|
|
|
|
|
|
|
|
```
|
|
|
{-# LANGUAGE DeriveAnyClass, GeneralizedNewtypeDeriving #-}classC a where
|
|
|
c :: a ->String
|
|
|
c _="default"instanceCIntwhere
|
|
|
{-# LANGUAGE DeriveAnyClass, GeneralizedNewtypeDeriving #-}
|
|
|
|
|
|
class C a where
|
|
|
c :: a -> String
|
|
|
c _ = "default"
|
|
|
|
|
|
instance C Int where
|
|
|
c = show
|
|
|
|
|
|
newtypeT=MkTIntderivingC
|
|
|
newtype T = MkT Int deriving C
|
|
|
```
|
|
|
|
|
|
|
... | ... | @@ -25,10 +31,17 @@ Currently, GHC will accept the above code by defaulting to the `DeriveAnyClass` |
|
|
prevents users from using `GeneralizedNewtypeDeriving` and `DeriveAnyClass` simultaneously.
|
|
|
|
|
|
|
|
|
|
|
|
There are some other shortcomings of instance deriving as well. For instance, one cannot derive "newtype-style" `Read` or `Show` instances. For example:
|
|
|
|
|
|
|
|
|
```
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}newtypeS=MkSIntderivingShowsOne::StringsOne= show (MkS1)
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
|
|
|
|
newtype S = MkS Int deriving Show
|
|
|
|
|
|
sOne :: String
|
|
|
sOne = show (MkS 1)
|
|
|
```
|
|
|
|
|
|
|
... | ... | @@ -42,11 +55,15 @@ A solution to the above issues is to introduce a syntax extension called *derivi |
|
|
- Deriving stock instances: This is the usual approach that GHC takes. For certain classes that GHC is aware of, such as `Eq`, `Ord`, `Functor`, `Generic`, and others, GHC can use an algorithm to derive an instance of the class for a particular datatype mechanically. For example, a stock derived `Eq` instance for `data Foo = Foo Int` is:
|
|
|
|
|
|
```
|
|
|
instanceEqFoowhereFoo a ==Foo b = a == b
|
|
|
instance Eq Foo where
|
|
|
Foo a == Foo b = a == b
|
|
|
```
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> Stock applies to the "standard" derivable typeclasses mentioned in the Haskell Report like `Eq` and `Show`, as well as some GHC-specific classes like `Data` and `Generic`. The stock strategy only requires enabling language extensions in certain cases (`DeriveFunctor`, `DeriveGeneric`, etc.).
|
|
|
>
|
|
|
>
|
|
|
|
|
|
- `GeneralizedNewtypeDeriving`: An approach that GHC only uses if the eponymous language extension is enabled, and if an instance is being derived for a newtype. GHC will reuse the instance of the newtype's underlying type to generate an instance for the newtype itself. For more information, see [ http://downloads.haskell.org/\~ghc/8.0.1/docs/html/users_guide/glasgow_exts.html\#generalised-derived-instances-for-newtypes](http://downloads.haskell.org/~ghc/8.0.1/docs/html/users_guide/glasgow_exts.html#generalised-derived-instances-for-newtypes)
|
|
|
- `DeriveAnyClass`: An approach that GHC only uses if the eponymous language extension is enabled. When this strategy is invoked, GHC will simply generate an instance with empty implementations for all methods. For more information, see [ http://downloads.haskell.org/\~ghc/8.0.1/docs/html/users_guide/glasgow_exts.html\#deriving-any-other-class](http://downloads.haskell.org/~ghc/8.0.1/docs/html/users_guide/glasgow_exts.html#deriving-any-other-class)
|
... | ... | @@ -61,11 +78,25 @@ While GHC can pick a strategy internally, users don't have a reliable way to pic |
|
|
### Examples
|
|
|
|
|
|
|
|
|
|
|
|
Here is an example showing off what `-XDerivingStrategies` allows:
|
|
|
|
|
|
|
|
|
```
|
|
|
{-# LANGUAGE DeriveAnyClass #-}{-# LANGUAGE DeriveFoldable #-}{-# LANGUAGE DeriveFunctor #-}{-# LANGUAGE DerivingStrategies #-}{-# LANGUAGE GeneralizedNewtypeDeriving #-}{-# LANGUAGE StandaloneDeriving #-}newtypeT a =T a
|
|
|
derivingShowderiving stock (Eq,Foldable)derivingnewtypeOrdderiving anyclass Readderiving stock instanceFunctorT
|
|
|
{-# LANGUAGE DeriveAnyClass #-}
|
|
|
{-# LANGUAGE DeriveFoldable #-}
|
|
|
{-# LANGUAGE DeriveFunctor #-}
|
|
|
{-# LANGUAGE DerivingStrategies #-}
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
|
{-# LANGUAGE StandaloneDeriving #-}
|
|
|
|
|
|
newtype T a = T a
|
|
|
deriving Show
|
|
|
deriving stock (Eq, Foldable)
|
|
|
deriving newtype Ord
|
|
|
deriving anyclass Read
|
|
|
|
|
|
deriving stock instance Functor T
|
|
|
```
|
|
|
|
|
|
|
... | ... | @@ -80,28 +111,49 @@ With `-XDerivingStrategies` in the picture, we can now state how GHC figures out |
|
|
|
|
|
1. If deriving a stock class:
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> (a) If deriving `Eq`, `Ord`, `Ix`, or `Bounded` for a newtype, use the `GeneralizedNewtypeDeriving` strategy (even if the language extension isn't enabled).
|
|
|
>
|
|
|
>
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> (b) If deriving `Functor`, `Foldable`, or `Enum` for a newtype, the datatype can be successfully used with `GeneralizedNewtypeDeriving`, and `-XGeneralizedNewtypeDeriving` has been enabled, use the `GeneralizedNewtypeDeriving` strategy.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> (c) Otherwise, if deriving a stock class and the corresponding language extension is enabled (if necessary), use the stock strategy. If the language extension is not enabled, throw an error.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
1. If not deriving a stock class:
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> (a) If deriving an instance for a newtype and both `-XGeneralizedNewtypeDeriving` and `-XDeriveAnyClass` are enabled, default to `DeriveAnyClass`, but emit a warning stating the ambiguity.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> (b) Otherwise, if `-XDeriveAnyClass` is enabled, use `DeriveAnyClass`.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> (c) Otherwise, if deriving an instance for a newtype, the datatype and typeclass can be successfully used with `GeneralizedNewtypeDeriving`, and `-XGeneralizedNewtypeDeriving` is enabled, do so.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> (d) Otherwise, throw an error.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
|
|
|
The stock classes are:
|
... | ... | @@ -127,9 +179,11 @@ The relationship between stock classes and `DeriveAnyClass` can be be summarized |
|
|
Step 2 is fairly intricate since GHC tries to use `GeneralizedNewtypeDeriving` in certain special cases whenever it can to optimize the generated instances. In addition, the phrase "can be successfully used with `GeneralizedNewtypeDeriving`" must be invoked since it is possible for `GeneralizedNewtypeDeriving` to fail for certain datatypes. For example, you cannot have a newtype-derived `Functor` instance for `newtype Compose f g a = Compose (f (g a))`, since the last type variable `a` cannot be eta-reduced.
|
|
|
|
|
|
|
|
|
|
|
|
To help visualize things, here's a table summarizing which typeclasses GHC decides it can use the `newtype` strategy for (thanks to Ørjan Johansen):
|
|
|
|
|
|
<table><tr><th></th>
|
|
|
|
|
|
<table><tr><th> </th>
|
|
|
<th> No extension required </th>
|
|
|
<th> Requires language extension to use
|
|
|
</th>
|
... | ... | @@ -137,19 +191,23 @@ To help visualize things, here's a table summarizing which typeclasses GHC decid |
|
|
<th></th></tr>
|
|
|
<tr><th> GND when possible </th>
|
|
|
<th> 2(a) </th>
|
|
|
<th>`Eq`, `Ord`, `Ix`, `Bounded`</th>
|
|
|
<th></th>
|
|
|
<th> <tt>Eq</tt>, <tt>Ord</tt>, <tt>Ix</tt>, <tt>Bounded</tt> </th>
|
|
|
<th>
|
|
|
</th>
|
|
|
<th></th></tr>
|
|
|
<tr><th> GND with extension </th>
|
|
|
<th> 2(b) </th>
|
|
|
<th>`Enum`</th>
|
|
|
<th> <tt>Enum</tt> </th>
|
|
|
<th> 2(b) </th>
|
|
|
<th>`Functor`, `Foldable`</th></tr>
|
|
|
<th> <tt>Functor</tt>, <tt>Foldable</tt>
|
|
|
</th></tr>
|
|
|
<tr><th> Never select GND </th>
|
|
|
<th> 2(c) </th>
|
|
|
<th>`Read`, `Show`</th>
|
|
|
<th> <tt>Read</tt>, <tt>Show</tt> </th>
|
|
|
<th> 2(c) </th>
|
|
|
<th>`Data`, `Generic`, `Generic1`, `Typeable`, `Traversable`, `Lift`</th></tr></table>
|
|
|
<th> <tt>Data</tt>, <tt>Generic</tt>, <tt>Generic1</tt>, <tt>Typeable</tt>, <tt>Traversable</tt>, <tt>Lift</tt>
|
|
|
</th></tr></table>
|
|
|
|
|
|
|
|
|
|
|
|
This provides another reason to use `-XDerivingStrategies`: trying to memorize this algorithm is almost impossible!
|
... | ... | @@ -163,14 +221,20 @@ Safe Haskell has some things to say about derived `Typeable` and `Generic` insta |
|
|
GHC currently disallows manually implementing `Typeable` instances, and derived `Typeable` instances are ignored, as GHC automatically generates `Typeable` instances for all datatypes, typeclasses, and promoted data constructors. Similarly, GHC will ignore derived `Typeable` instances even if a deriving strategy is used.
|
|
|
|
|
|
|
|
|
|
|
|
GHC also disallows manually implementing `Generic` instances when `-XSafe` is enabled, so the only way to declare `Generic` instances in Safe Haskell is to use the `-XDeriveGeneric` extension. To preserve this property, it is forbidden to derive a `Generic` instance with a deriving strategy other than `stock`.
|
|
|
|
|
|
|
|
|
### What `-XDerivingStrategies` is not
|
|
|
|
|
|
|
|
|
|
|
|
`-XDerivingStrategies` is not intended to be a catch-all language extension that enables all of `-XDeriveFunctor`, `-XDeriveAnyClass`, `-XGeneralizedNewtypeDeriving`, `-XDeriveGeneric`, and all other exotic `deriving` language extensions. To see why consider the following code:
|
|
|
|
|
|
|
|
|
```
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}newtypeT=MkTSderiving(Foo,Bar)
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
|
newtype T = MkT S deriving (Foo, Bar)
|
|
|
```
|
|
|
|
|
|
|
... | ... | @@ -187,62 +251,103 @@ Several alternative syntaxes and keyword suggestions have been proposed in the o |
|
|
- Use pragmas instead of keywords. We could indicate the use of deriving strategies like so:
|
|
|
|
|
|
```
|
|
|
newtypeT a =T a
|
|
|
derivingShowderiving{-# STOCK #-}(Eq,Foldable)deriving{-# NEWTYPE #-}Ordderiving{-# ANYCLASS #-}Readderiving{-# STOCK #-}instanceFunctorT
|
|
|
newtype T a = T a
|
|
|
deriving Show
|
|
|
deriving {-# STOCK #-} (Eq, Foldable)
|
|
|
deriving {-# NEWTYPE #-} Ord
|
|
|
deriving {-# ANYCLASS #-} Read
|
|
|
|
|
|
deriving {-# STOCK #-} instance Functor T
|
|
|
```
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> This has the advantage of being backwards compatible. On the other hand, several people objected to this idea on the basis that the presence of pragmas shouldn't affect the semantics of programs.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
- Use type synonyms instead of keywords. We could have three builtin type syonyms:
|
|
|
|
|
|
```
|
|
|
typeStock(a :: k)= a
|
|
|
typeNewtype(a :: k)= a
|
|
|
typeAnyClass(a :: k)= a
|
|
|
type Stock (a :: k) = a
|
|
|
type Newtype (a :: k) = a
|
|
|
type AnyClass (a :: k) = a
|
|
|
```
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> that we imbue with compiler magic to indicate the presence of a deriving strategy. For example:
|
|
|
>
|
|
|
>
|
|
|
|
|
|
```
|
|
|
newtypeT a =T a deriving(StockEq,NewtypeOrd,AnyClassRead,Show)derivinginstanceStock(FunctorT)
|
|
|
newtype T a = T a deriving (Stock Eq, Newtype Ord, AnyClass Read, Show)
|
|
|
deriving instance Stock (Functor T)
|
|
|
```
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> This is fairly backwards compatible (back to GHC 7.6), and would require absolutely no Template Haskell or parser changes. On the other hand, it requires making type synonyms behave magically, and it muddies up the actual class being derived, perhaps making it more confusing to look at.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
- Instead of allowing multiple `deriving` clauses per datatype, one could indicate the presence of a deriving strategy by preceding every derived class with the appropriate keyword. For example:
|
|
|
|
|
|
```
|
|
|
newtypeT a =T a
|
|
|
deriving(Show, stock Eq, stock Foldable,newtypeOrd, anyclass Read)
|
|
|
newtype T a = T a
|
|
|
deriving ( Show
|
|
|
, stock Eq
|
|
|
, stock Foldable
|
|
|
, newtype Ord
|
|
|
, anyclass Read
|
|
|
)
|
|
|
```
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> This is cleaner in some respects, as it more closely resembles an English-language description of how to derive all the instances that `T` needs (`Eq` via stock, `Ord` via newtype, `Read` via anyclass...). A downside is that this is much trickier (though likely not impossible) to parse, since all-lowercase words can be confused for type variables.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> Another factor to consider is the semantic noise that this suggestion brings. Unlike with multiple `deriving` clauses, this suggestion requires the use of a keyword next to *every* class. This can lead to more keystrokes than the multi-clause suggestion would when you derive several instances with the same deriving strategy. For instance, in the above example you need to type `stock` twice, whereas in the multi-clause proposal you need only type `stock` once. This can really add up when you derive loads of instances at a time, e.g.,
|
|
|
>
|
|
|
>
|
|
|
|
|
|
```
|
|
|
newtypeT a =T a
|
|
|
deriving(newtypeA,newtypeB,newtypeC,newtypeD,newtypeE,newtypeF)
|
|
|
newtype T a = T a
|
|
|
deriving ( newtype A
|
|
|
, newtype B
|
|
|
, newtype C
|
|
|
, newtype D
|
|
|
, newtype E
|
|
|
, newtype F
|
|
|
)
|
|
|
```
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> This can be expressed much more succintly as:
|
|
|
>
|
|
|
>
|
|
|
|
|
|
```
|
|
|
newtypeT a =T a
|
|
|
derivingnewtype(A,B,C,D,E,F)
|
|
|
newtype T a = T a
|
|
|
deriving newtype (A, B, C, D, E, F)
|
|
|
```
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> In addition, I (the proposal author, Ryan) would argue that it's tidier to put the strategy keyword outside of the parentheses, since it makes it clear that these keywords aren't modifying the type we're deriving, only the *means* by which we're deriving it.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
>
|
|
|
>
|
|
|
> One could also argue that GHC should support both syntaxes, although it would combine the downsides of both for questionable gain.
|
|
|
>
|
|
|
>
|
|
|
|
|
|
- Previous alternative suggestions for the `stock` keyword were `bespoke`, `builtin`, `magic`, `wiredin`, `standard`, `native`, `original`, and `specialized`. In particular, `builtin` is what I (Ryan) originally suggested, but it was poorly received since *all* deriving extensions are, to some extent, built-in to GHC. I then championed `bespoke`, but others expressed reservations about the word's relative obscurity outside of Commonwealth English.
|
|
|
- A previous alternative suggestion for the `anyclass` keyword was `default`, since it's already a keyword, and the connection to `-XDefaultSignatures` would be evocative of generic programming, which `-XDeriveAnyClass` is often used for. On the other hand, I (Ryan) felt it would be too easy to confuse with what `stock` accomplishes (i.e., the "default" GHC behavior when deriving an instance), so I proposed `anyclass` to make it very explicit what's going on there. |