You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: pages/docs/manual/v12.0.0/generalized-algebraic-data-types.mdx
+15-11Lines changed: 15 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -91,14 +91,13 @@ we try to return a standard timezone from this function. Actually, this seems li
91
91
we still want to be able to match on all cases of the variant sometimes, and a naive attempt at this will not pass the type checker. A naive example will fail:
92
92
93
93
```res example
94
-
let convertToDaylight = tz => {
94
+
let convertToDaylight = tz =>
95
95
switch tz {
96
96
| EST => EDT
97
97
| CST => CDT
98
98
| CDT => CDT
99
99
| EDT => EDT
100
100
}
101
-
}
102
101
```
103
102
104
103
This will complain that `daylight` and `standard` are incompatible. To fix this, we need to explicitly annotate to tell the compiler to accept both:
@@ -107,11 +106,14 @@ This will complain that `daylight` and `standard` are incompatible. To fix this,
107
106
let convertToDaylight : type a. timezone<a> => timezone<daylight> = // ...
108
107
```
109
108
110
-
`type a.` here defines a _locally abstract type_ which basically tells the compiler that the type parameter a is some specific type, but we don't care what it is. The cost of the extra specificity and safety that GADTs give us is that the compiler is not able to help us with type inference as much.
109
+
The syntax `type a.` here defines a _locally abstract type_ which basically tells the compiler that the type parameter a is some specific type, but we don't care what it is. The cost of the extra specificity and safety that
110
+
GADTs give us is that the compiler less able to help us with type inference.
111
111
112
112
## Varying return type
113
113
114
-
Sometimes, a function should have a different return type based on what you give it, and GADTs are how we can do this in a type-safe way. We can implement a generic `add` function that works on both `int` or `float`:
114
+
Sometimes, a function should have a different return type based on what you give it, and GADTs are how we can do this in a type-safe way. We can implement a generic `add` function[^1] that works on both `int` or `float`:
115
+
116
+
[^1]: In ReScript v12, the built-in operators are already generic, but we use them in this example for simplicity.
115
117
116
118
```res example
117
119
type rec number<_> = Int(int): number<int> | Float(float): number<float>
@@ -131,7 +133,9 @@ let bar = add(Int(1), Float(2.0)) // the compiler will complain here
131
133
132
134
How does this work? The key thing is the function signature for add. The `number` GADT is acting as a _type witness_. We have told the compiler that the type parameter for `number` will be the same as the type we return -- both are set to `a`. So if we provide a `number<int>`, `a` equals `int`, and the function will therefore return an `int`.
133
135
134
-
We can also use this to avoid returning `option` unnecessarily. This example is adapted from Real World Ocaml, chapter 9. We create an array searching function can be configured to either raise an exception, return an `option`, or provide a `default` value depending on the behavior we want.
136
+
We can also use this to avoid returning `option` unnecessarily. We create an array searching function which either raises an exception, returns an `option`, or provides a `default` value depending on the behavior we ask for.[^2]
137
+
138
+
[^2]: This example is adapted from [here](https://dev.realworldocaml.org/gadts.html).
135
139
136
140
```res example
137
141
module If_not_found = {
@@ -171,7 +175,7 @@ In an advanced case that combines the above techniques, we can use GADTs to sele
171
175
The below example defines a `num` type similar to our above addition example, but this lets us use `int` and `float` arrays
172
176
interchangeably, hiding the implementation type rather than exposing it. This is similar to a regular variant. However, it is a tuple including embedding a `numTy` and another value.
173
177
`numTy` serves as a type-witness, making it
174
-
possible to recover type information that was hidden dynamically. Matching on `numTy` will "reveal" the type of the other value in the pair.We can use this to write a generic sum function over arrays of numbers:
178
+
possible to recover type information that was hidden dynamically. Matching on `numTy` will "reveal" the type of the other value in the pair.We can use this to write a generic sum function over arrays of numbers:
175
179
176
180
```res example
177
181
type rec numTy<'a> =
@@ -197,8 +201,8 @@ Javascript libraries that are highly polymorphic or use inheritance can benefit
197
201
of Node's `Stream` API.
198
202
199
203
This API has a method for binding event handlers, `on`. This takes an event and a callback. The callback accepts different parameters
200
-
depending in which event we are binding to. A naive implementation might look similar to this, defining a
201
-
separate method for each stream event to wrap the unsafe version of on.
204
+
depending on which event we are binding to. A naive implementation might look similar to this, defining a
205
+
separate method for each stream event to wrap the unsafe version of `on`.
202
206
203
207
```res example
204
208
module Stream = {
@@ -223,7 +227,7 @@ the type signature of the callback and pass this instead of a plain string.
223
227
Additionally, we use some type parameters to represent the different types of Streams.
224
228
225
229
This example is complex, but it enforces tons of useful rules. The wrong event can never be used
226
-
with the wrong callback, but it also will never be used with the wrong kind of stream. The compiler will complain for example if we try to use a `Pipe` event with anything other than a `writable` stream.
230
+
with the wrong callback, but it also will never be used with the wrong kind of stream. The compiler will for example complain if we try to use a `Pipe` event with anything other than a `writable` stream.
227
231
228
232
The real magic happens in the signature of `on`. Read it carefully, and then look at the examples and try to
229
233
follow how the type variables are getting filled in, write it out on paper what each type variable is equal
@@ -242,7 +246,7 @@ module Stream = {
242
246
@unboxed
243
247
type chunk =
244
248
| Str(string)
245
-
// Node uses actually its own buffer type, but for the tutorial are just using the stdlib's buffer type.
249
+
// Node uses actually its own buffer type, but for the tutorial we are using the stdlib's buffer type.
246
250
| Buf(buffer)
247
251
248
252
type rec event<_, _> =
@@ -287,7 +291,7 @@ encounter will be more difficult to understand, and the compiler sometimes requi
287
291
type your code.
288
292
289
293
However, there are definite situations where GADTs are the _right_ decision
290
-
and will _simplify_ your code and help you avoid bugs, even rendering some bugs impossible. The `Stream` example above is a good example where the "simpler" alternative of using regular variants or even strings.
294
+
and will _simplify_ your code and help you avoid bugs, even rendering some bugs impossible. The `Stream` example above is a good example where the "simpler" alternative of using regular variants or even strings
291
295
would lead to a much more complex and error prone interface.
292
296
293
297
Ordinary variants are not necessarily _simple_ therefore, and neither are GADTs necessarily _complex_.
0 commit comments