Skip to content

Commit 37b3fcb

Browse files
code formatting fixed in GADT man page
1 parent e429217 commit 37b3fcb

File tree

1 file changed

+44
-40
lines changed

1 file changed

+44
-40
lines changed

pages/docs/manual/v12.0.0/generalized-algebraic-data-types.mdx

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,19 @@ description: "Generalized Algebraic Data Types in Rescript"
44
canonical: "/docs/manual/v12.0.0/generalized-algebraic-data-types"
55
---
66

7-
Generalized Algebraic Data Types
8-
====
7+
# Generalized Algebraic Data Types
98

109
Generalized Algebraic Data Types (GADTs) are an advanced feature of Rescript's type system. "Generalized" can be somewhat of a misnomer -- what they actually allow you to do is add some extra type-specificity to your variants. Using a GADT, you can give the individual cases of a variant _different_ types.
1110

1211
For a quick overview of the use cases, reach for GADTs when:
12+
1313
1. You need to distinguish between different members of a variant at the type-level
1414
2. You want to "hide" type information in a type-safe way, without resorting to casts.
1515
3. You need a function to return a different type depending on its input.
1616

1717
GADTs usually are overkill, but when you need them, you need them! Understanding them from first principles is difficult, so it is best to explain through some motivating examples.
1818

19-
Distinguishing Constructors (Subtyping)
20-
----
19+
## Distinguishing Constructors (Subtyping)
2120

2221
Suppose a simple variant type that represents the current timezone of a date value. This handles both daylight savings and standard time:
2322

@@ -28,8 +27,10 @@ type timezone =
2827
| CST // standard time
2928
| CDT // daylight time
3029
// etc...
31-
```
30+
```
31+
3232
Using this variant type, we will end up having functions like this:
33+
3334
```res example
3435
let convert_to_daylight = tz => {
3536
switch tz {
@@ -51,10 +52,12 @@ type daylight_or_standard =
5152
```
5253

5354
This has a lot of problems. For one, it's cumbersome and redundant. We would now have to pattern-match twice whenever we deal with a timezone that's wrapped up here. The compiler will force us to check whether we are dealing with daylight or standard time, but notice that there's nothing stopping us from providing invalid timezones to these constructors:
55+
5456
```res example
5557
let invalid_tz1 = Daylight(EST)
5658
let invalid_tz2 = Standard(EDT)
5759
```
60+
5861
Consequently, we still have to write our redundant catchall cases. We could define daylight savings time and standard time as two _separate_ types, and unify those in our `daylight_or_standard` variant. That could be a passable solution, but that makes a distinction really would like to do is implement some kind of _subtyping_ relationship. We have two _kinds_ of timezone. This is where GADTs are handy:
5962

6063
```res example
@@ -66,8 +69,9 @@ type rec timezone<_> =
6669
| EDT: timezone<daylight>
6770
| CST: timezone<standard>
6871
| CDT: timezone<daylight>
69-
```
70-
We define our type with a type parameter. We manually annotate each constructor, providing it with the correct type parameter indicating whether it is standard or daylight. Each constructor is a `timezone`,
72+
```
73+
74+
We define our type with a type parameter. We manually annotate each constructor, providing it with the correct type parameter indicating whether it is standard or daylight. Each constructor is a `timezone`,
7175
but we've added another level of specificity using a type parameter. Constructors are now understood to be `standard` or `daylight` at the _type_ level. Now we can fix our function like this:
7276

7377
```res example
@@ -80,8 +84,8 @@ let convert_to_daylight = tz => {
8084
```
8185

8286
The compiler can infer correctly that this function should only take `timezone<standard>` and only output
83-
`timezone<daylight>`. We don't need to add any redundant catchall cases and the compiler will even error if
84-
we try to return a standard timezone from this function. Actually, this seems like it could be a problem,
87+
`timezone<daylight>`. We don't need to add any redundant catchall cases and the compiler will even error if
88+
we try to return a standard timezone from this function. Actually, this seems like it could be a problem,
8589
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:
8690

8791
```res example
@@ -103,8 +107,8 @@ let convert_to_daylight : type a. timezone<a> => timezone<daylight> = // ...
103107

104108
`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.
105109

106-
Varying return type
107-
----
110+
## Varying return type
111+
108112
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`:
109113

110114
```res example
@@ -129,7 +133,7 @@ We can also use this to avoid returning `option` unnecessarily. This example is
129133

130134
```res example
131135
module If_not_found = {
132-
type t<_,_>
136+
type t<_,_>
133137
}module IfNotFound = {
134138
type rec t<_, _> =
135139
| Raise: t<'a, 'a>
@@ -159,8 +163,8 @@ let flexible_find:
159163
160164
```
161165

162-
Hide and recover Type information Dynamically
163-
---
166+
## Hide and recover Type information Dynamically
167+
164168
In a very advanced case that combines many of the above techniques, we can use GADTs to selectively hide and recover type information. This helps us create more generic types.
165169
The below example defines a `num` type similar to our above addition example, but this lets us use `int` and `float` arrays
166170
interchangeably, hiding the implementation type rather than exposing it. This is similar to a regular variant. However, it is a tuple including embedding a `num_ty` and another value.
@@ -185,13 +189,13 @@ let sum = (Narray(witness, array)) => {
185189
}
186190
```
187191

188-
A Practical Example -- writing bindings:
189-
---
190-
Javascript libraries that are highly polymorphic or use inheritance can benefit hugely from GADTs, but they can be useful for bindings even in other cases. The following examples are writing bindings to a simplified
192+
## A Practical Example -- writing bindings:
193+
194+
Javascript libraries that are highly polymorphic or use inheritance can benefit hugely from GADTs, but they can be useful for bindings even in other cases. The following examples are writing bindings to a simplified
191195
of Node's `Stream` API.
192196

193-
This API has a method for binding event handlers, `on`. This takes an event and a callback. The callback accepts different parameters
194-
depending in which event we are binding to. A naive implementation might look similar to this, defining a
197+
This API has a method for binding event handlers, `on`. This takes an event and a callback. The callback accepts different parameters
198+
depending in which event we are binding to. A naive implementation might look similar to this, defining a
195199
separate method for each stream event to wrap the unsafe version of on.
196200

197201
```res example
@@ -207,11 +211,11 @@ module Stream = {
207211
}
208212
```
209213

210-
Not only is this quite tedious to write, and quite ugly, but we gain very little from it. The function wrappers even add performance overhead, so we are losing on almost all fronts. If we define subtypes of
211-
Stream like `Readable` or `Writable`, which have all sorts of special interactions with the callback that jeopardize our type-safety, we are going to be in even deeper trouble.
214+
Not only is this quite tedious to write, and quite ugly, but we gain very little from it. The function wrappers even add performance overhead, so we are losing on almost all fronts. If we define subtypes of
215+
Stream like `Readable` or `Writable`, which have all sorts of special interactions with the callback that jeopardize our type-safety, we are going to be in even deeper trouble.
212216

213-
Instead, we can use the same GADT technique that let us vary return type to vary the input type.
214-
Not only are we able to now just use a single method, but the compiler will guarantee we are always using the correct callback type for the given event. We simply define an event GADT which specifies
217+
Instead, we can use the same GADT technique that let us vary return type to vary the input type.
218+
Not only are we able to now just use a single method, but the compiler will guarantee we are always using the correct callback type for the given event. We simply define an event GADT which specifies
215219
the type signature of the callback and pass this instead of a plain string.
216220

217221
Additionally, we use some type parameters to represent the different types of Streams.
@@ -220,7 +224,7 @@ This example is complex, but it enforces tons of useful rules. The wrong event c
220224
with the wrong callback, but it also will never be used with the wrong kind of stream. The compiler will will complain for example if we try to use a `Pipe` event with anything other than a `writable` stream.
221225

222226
The real magic happens in the signature of `on`. Read it carefully, and then look at the examples and try to
223-
follow how the type variables are getting filled in, write it out on paper what each type variable is equal
227+
follow how the type variables are getting filled in, write it out on paper what each type variable is equal
224228
to if you need and it will soon become clear.
225229

226230
```res example
@@ -232,7 +236,7 @@ module Stream = {
232236
type readable
233237
234238
type buffer = {buffer: ArrayBuffer.t}
235-
239+
236240
@unboxed
237241
type chunk =
238242
| Str(string)
@@ -270,21 +274,21 @@ writer->Stream.on(End, _ => Js.log("End reached"))
270274
271275
```
272276

273-
This example is only over a tiny, imaginary subset of node's Stream API, but it shows a real-life example
277+
This example is only over a tiny, imaginary subset of node's Stream API, but it shows a real-life example
274278
where GADTs are all but indispensable.
275279

276-
Conclusion
277-
-----
280+
## Conclusion
281+
278282
While GADTs can make your types extra-expressive and get more safety, with great power comes great
279-
responsibility. Code that uses GADTs can sometimes be too clever for its own good. The type errors you
280-
encounter will be more difficult to understand, and the compiler sometimes requires extra help to properly
281-
type your code.
282-
283-
However, There are definite situations where GADTs are the _right_ decision
284-
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.
285-
would lead to a much more complex and error prone interface.
286-
287-
Ordinary variants are not necessarily _simple_ therefore, and neither are GADTs necessarily _complex_.
288-
The choice is rather which tool is the right one for the job. When your logic is complex, the highly expressive nature of GADTs can make it simpler to capture that logic.
289-
When your logic is simple, it's best to reach for a simpler tool and avoid the cognitive overhead.
290-
The only way to get good at identifying which the situation calls for is to try out
283+
responsibility. Code that uses GADTs can sometimes be too clever for its own good. The type errors you
284+
encounter will be more difficult to understand, and the compiler sometimes requires extra help to properly
285+
type your code.
286+
287+
However, There are definite situations where GADTs are the _right_ decision
288+
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.
289+
would lead to a much more complex and error prone interface.
290+
291+
Ordinary variants are not necessarily _simple_ therefore, and neither are GADTs necessarily _complex_.
292+
The choice is rather which tool is the right one for the job. When your logic is complex, the highly expressive nature of GADTs can make it simpler to capture that logic.
293+
When your logic is simple, it's best to reach for a simpler tool and avoid the cognitive overhead.
294+
The only way to get good at identifying which the situation calls for is to try out

0 commit comments

Comments
 (0)