Skip to content

Commit 2859217

Browse files
authored
Add more Type Class deriving details (documentationjs#338)
1 parent 6e58684 commit 2859217

File tree

2 files changed

+208
-14
lines changed

2 files changed

+208
-14
lines changed

guides/Type-Class-Deriving.md

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
## Type Class Deriving
2+
3+
The compiler can derive type class instances to spare you the tedium of writing boilerplate. There are a few ways to do this depending on the specific type and class being derived.
4+
5+
### Classes with built-in compiler support
6+
7+
Some classes have special built-in compiler support, and their instances can be derived from all types.
8+
9+
For example, if you you'd like to be able to remove duplicates from an array of an ADT using `nub`, you need an `Eq` and `Ord` instance. Rather than writing these manually, let the compiler do the work.
10+
11+
```purs
12+
import Data.Array (nub)
13+
14+
data MyADT
15+
= Some
16+
| Arbitrary Int
17+
| Contents Number String
18+
19+
derive instance eqMyADT :: Eq MyADT
20+
derive instance ordMyADT :: Ord MyADT
21+
22+
nub [Some, Arbitrary 1, Some, Some] == [Some, Arbitrary 1]
23+
```
24+
25+
Currently, instances for the following classes can be derived by the compiler:
26+
- [Data.Generic.Rep (class Generic)](https://pursuit.purescript.org/packages/purescript-generics-rep/docs/Data.Generic.Rep#t:Generic)
27+
- [Data.Eq (class Eq)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Eq#t:Eq)
28+
- [Data.Ord (class Ord)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Ord#t:Ord)
29+
- [Data.Functor (class Functor)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Functor#t:Functor)
30+
- [Data.Newtype (class Newtype)](https://pursuit.purescript.org/packages/purescript-newtype/docs/Data.Newtype#t:Newtype)
31+
32+
### Derive from `newtype`
33+
34+
If you would like your newtype to defer to the instance that the underlying type uses for a given class, then you can use newtype deriving via the `derive newtype` keywords.
35+
36+
For example, let's say you want to add two `Score` values using the `Semiring` instance of the wrapped `Int`.
37+
38+
```purs
39+
newtype Score = Score Int
40+
41+
derive newtype instance semiringScore :: Semiring Score
42+
43+
tenPoints :: Score
44+
tenPoints = (Score 4) + (Score 6)
45+
```
46+
47+
That `derive` line replaced all this code:
48+
49+
```purs
50+
-- No need to write this
51+
instance semiringScore :: Semiring Score where
52+
zero = Score 0
53+
add (Score a) (Score b) = Score (a + b)
54+
mul (Score a) (Score b) = Score (a * b)
55+
one = Score 1
56+
```
57+
58+
Note that we can use either of these options to derive an `Eq` instance for a `newtype`, since `Eq` has built-in compiler support. They are equivalent in this case.
59+
60+
```purs
61+
derive instance eqScore :: Eq Score
62+
derive newtype instance eqScore :: Eq Score
63+
```
64+
65+
### Deriving from `Generic`
66+
67+
The compiler's built-in support for `Generic` unlocks convenient deriving for many other classes not listed above.
68+
69+
For example, if we wanted to derive a `Show` instance for `MyADT` it might seem like we're out of luck: `Show` is not a class with built-in compiler support for deriving and `MyADT` is not a `newtype` (so we can't use newtype deriving).
70+
71+
But we _can_ use `genericShow`, which works with _any_ type that has a `Generic` instance. And recall that the compiler has built-in support for deriving a `Generic` instance for any type (including the `MyADT` type). We put all those pieces together like so:
72+
73+
```purescript
74+
import Data.Generic.Rep (class Generic)
75+
import Data.Generic.Rep.Show (genericShow)
76+
import Effect.Console (logShow)
77+
78+
derive instance genericMyADT :: Generic MyADT _
79+
80+
instance showMyADT :: Show MyADT where
81+
show = genericShow
82+
83+
main = logShow [Some, Arbitrary 1, Contents 2.0 "Three"]
84+
-- Prints:
85+
-- [Some,(Arbitrary 1),(Contents 2.0 "Three")]
86+
```
87+
88+
The `Show` type class is most often used for debugging data, so the output of most `Show` instances can be copy-pasted back into a PureScript source file to reconstruct the original data. The `Show` instance we created by deriving `Generic` and then using `genericShow` follows this convention.
89+
90+
This is a good opportunity to emphasize how newtype deriving is different from instances derived by the compiler or through the `Generic` type class. In the examples below, notice how the instance derived through `Generic` includes the newtype constructor `Score`, but the newtype-derived instance simply reuses the underlying `Show` instance for `Int` and therefore does not include the constructor:
91+
92+
```purs
93+
import Effect.Console (logShow)
94+
95+
newtype Score = Score Int
96+
97+
-- newtype deriving omits wrapper with show
98+
derive newtype instance showScore :: Show Score
99+
100+
main = logShow (Score 5)
101+
-- Prints:
102+
-- 5
103+
```
104+
105+
```purs
106+
import Data.Generic.Rep (class Generic)
107+
import Data.Generic.Rep.Show (genericShow)
108+
import Effect.Console (logShow)
109+
110+
newtype Score = Score Int
111+
112+
-- generic deriving prints wrapper with show
113+
derive instance genericScore :: Generic Score _
114+
instance showScore :: Show Score where
115+
show = genericShow
116+
117+
main = logShow (Score 5)
118+
-- Prints:
119+
-- (Score 5)
120+
```
121+
122+
More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). See this [blog post](https://harry.garrood.me/blog/write-your-own-generics/) for a tutorial on how to write your own `generic` functions.
123+
124+
#### Avoiding stack overflow errors with recursive types
125+
126+
Be careful when using generic functions with recursive data types. Due to strictness, these instances _cannot_ be written in point free style:
127+
128+
```purs
129+
import Data.Generic.Rep (class Generic)
130+
import Data.Generic.Rep.Show (genericShow)
131+
import Effect.Console (logShow)
132+
133+
data Chain a
134+
= End a
135+
| Link a (Chain a)
136+
137+
derive instance genericChain :: Generic (Chain a) _
138+
139+
instance showChain :: Show a => Show (Chain a) where
140+
show c = genericShow c -- Note the use of the seemingly-unnecessary variable `c`
141+
142+
main = logShow $ Link 1 $ Link 2 $ End 3
143+
-- Prints:
144+
-- (Link 1 (Link 2 (End 3)))
145+
```
146+
147+
If the instance was written in point free style, then would produce a stack overflow error:
148+
149+
``` purs
150+
instance showChain :: Show a => Show (Chain a) where
151+
show = genericShow -- This line is problematic
152+
153+
-- Throws this error:
154+
-- RangeError: Maximum call stack size exceeded
155+
```
156+
157+
This technique of undoing point free notation is known as _eta expansion_.
158+

language/Type-Classes.md

+50-14
Original file line numberDiff line numberDiff line change
@@ -137,35 +137,71 @@ See also the section in [PureScript by Example](https://book.purescript.org/chap
137137

138138
## Type Class Deriving
139139

140-
Some type class instances can be derived automatically by the PureScript compiler. To derive a type class instance, use the `derive instance` keywords:
140+
The compiler can derive type class instances to spare you the tedium of writing boilerplate. There are a few ways to do this depending on the specific type and class being derived.
141141

142-
```purescript
143-
newtype Person = Person { name :: String, age :: Int }
142+
### Classes with built-in compiler support
143+
144+
Some classes have special built-in compiler support, and their instances can be derived from all types.
145+
146+
For example, if you you'd like to be able to remove duplicates from an array of an ADT using `nub`, you need an `Eq` and `Ord` instance. Rather than writing these manually, let the compiler do the work.
147+
148+
```purs
149+
import Data.Array (nub)
144150
145-
derive instance eqPerson :: Eq Person
146-
derive instance ordPerson :: Ord Person
151+
data MyADT
152+
= Some
153+
| Arbitrary Int
154+
| Contents Number String
155+
156+
derive instance eqMyADT :: Eq MyADT
157+
derive instance ordMyADT :: Ord MyADT
158+
159+
nub [Some, Arbitrary 1, Some, Some] == [Some, Arbitrary 1]
147160
```
148-
Currently, the following type classes can be derived by the compiler:
149161

162+
Currently, instances for the following classes can be derived by the compiler:
150163
- [Data.Generic.Rep (class Generic)](https://pursuit.purescript.org/packages/purescript-generics-rep/docs/Data.Generic.Rep#t:Generic)
151164
- [Data.Eq (class Eq)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Eq#t:Eq)
152165
- [Data.Ord (class Ord)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Ord#t:Ord)
153166
- [Data.Functor (class Functor)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Functor#t:Functor)
154167
- [Data.Newtype (class Newtype)](https://pursuit.purescript.org/packages/purescript-newtype/docs/Data.Newtype#t:Newtype)
155168

156-
Note that `derive instance` is not the only mechanism for allowing you to avoid writing out boilerplate type class instance code. Many type classes not listed here can be derived through other means, such as via a Generic instance. For example, here's how to create a `Show` instance for `Person` via `genericShow`:
169+
### Derive from `newtype`
157170

158-
```purescript
159-
import Data.Generic.Rep (class Generic)
160-
import Data.Generic.Rep.Show (genericShow)
171+
If you would like your newtype to defer to the instance that the underlying type uses for a given class, then you can use newtype deriving via the `derive newtype` keywords.
172+
173+
For example, let's say you want to add two `Score` values using the `Semiring` instance of the wrapped `Int`.
161174

162-
derive instance genericPerson :: Generic Person _
175+
```purs
176+
newtype Score = Score Int
163177
164-
instance showPerson :: Show Person where
165-
show = genericShow
178+
derive newtype instance semiringScore :: Semiring Score
179+
180+
tenPoints :: Score
181+
tenPoints = (Score 4) + (Score 6)
182+
```
183+
184+
That `derive` line replaced all this code:
185+
186+
```purs
187+
-- No need to write this
188+
instance semiringScore :: Semiring Score where
189+
zero = Score 0
190+
add (Score a) (Score b) = Score (a + b)
191+
mul (Score a) (Score b) = Score (a * b)
192+
one = Score 1
166193
```
167194

168-
More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep).
195+
Note that we can use either of these options to derive an `Eq` instance for a `newtype`, since `Eq` has built-in compiler support. They are equivalent in this case.
196+
197+
```purs
198+
derive instance eqScore :: Eq Score
199+
derive newtype instance eqScore :: Eq Score
200+
```
201+
202+
### Deriving from `Generic`
203+
204+
The compiler's built-in support for `Generic` unlocks convenient deriving for many other classes not listed above. See the [deriving guide](../guides/Type-Class-Deriving.md#deriving-from-generic) for more information.
169205

170206
## Compiler-Solvable Type Classes
171207

0 commit comments

Comments
 (0)