kind-adt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMMERCIAL_LICENSE.md +25 -0
- package/LICENSE +373 -0
- package/README.md +627 -0
- package/index.d.ts +1455 -0
- package/index.js +346 -0
- package/package.json +36 -0
- package/utils.d.ts +57 -0
- package/utils.js +172 -0
package/README.md
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
<h1 align="center">kind-adt</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
🪴 The <strong>kind</strong> of <strong>ADTs</strong> you can count on in <strong>TypeScript</strong>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/kind-adt">
|
|
9
|
+
<img src="https://img.shields.io/npm/dm/kind-adt.svg" alt="downloads" height="18">
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/kind-adt">
|
|
12
|
+
<img src="https://img.shields.io/npm/v/kind-adt.svg" alt="npm version" height="18">
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://bundlephobia.com/package/kind-adt">
|
|
15
|
+
<img src="https://img.shields.io/bundlephobia/minzip/kind-adt.svg" alt="minzipped size" height="18">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://coveralls.io/github/Snowflyt/kind-adt?branch=main">
|
|
18
|
+
<img src="https://img.shields.io/coverallsCoverage/github/Snowflyt/kind-adt?branch=main" alt="coverage status" height="18">
|
|
19
|
+
</a>
|
|
20
|
+
<a href="https://github.com/Snowflyt/kind-adt">
|
|
21
|
+
<img src="https://img.shields.io/npm/l/kind-adt.svg" alt="MPL-2.0 license" height="18">
|
|
22
|
+
</a>
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { type Data, make } from "kind-adt";
|
|
27
|
+
import type { Arg0, HKT } from "hkt-core";
|
|
28
|
+
|
|
29
|
+
// Define an ADT
|
|
30
|
+
export type Option<T> = Data<
|
|
31
|
+
Some: [value: T],
|
|
32
|
+
None: [],
|
|
33
|
+
>;
|
|
34
|
+
|
|
35
|
+
// Generate constructors and match functions for the ADT
|
|
36
|
+
export const { Some, None, match } = make<OptionHKT>();
|
|
37
|
+
interface OptionHKT extends HKT { // <- Lift it to HKT
|
|
38
|
+
return: Option<Arg0<this>>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function safeDivide(n: number, d: number) {
|
|
42
|
+
// ^?: (n: number, d: number) => Option<number>
|
|
43
|
+
if (d === 0) return None;
|
|
44
|
+
return Some(n / d);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Pattern matching for ADTs
|
|
48
|
+
match(safeDivide(42, 2), {
|
|
49
|
+
Some: (n) => console.log("Result:", n),
|
|
50
|
+
None: () => console.log("Division by zero!"),
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- _One_ type to define your **A**lgebraic **D**ata **T**ype (`Data`).
|
|
57
|
+
- _One_ function to create **constructors**, **deconstructors**, **type guards** and **pattern matching function** for your ADT with **type safety** (`make`).
|
|
58
|
+
- [**Readable type signatures**](#provide-more-readable-type-signatures) for your ADT with _labeled tuples_.
|
|
59
|
+
- [**Recursive ADTs**](#recursive-adts) with ease.
|
|
60
|
+
- Tiny footprint (~1kB minzipped).
|
|
61
|
+
- Debug your ADTs with [optional utilities](#optional-utilities) ([`println`](#println) and [`show`](#show)) from `kind-adt/utils`.
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
To install **kind-adt** via npm (or any other package manager you prefer):
|
|
66
|
+
|
|
67
|
+
```shell
|
|
68
|
+
npm install kind-adt
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quickstart
|
|
72
|
+
|
|
73
|
+
**ADTs** ([**A**lgebraic **D**ata **T**ypes](https://en.wikipedia.org/wiki/Algebraic_data_type)) are just **_discriminated unions_** in TypeScript.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import type { Data } from "kind-adt";
|
|
77
|
+
|
|
78
|
+
export type Option<T> = Data<
|
|
79
|
+
Some: [T],
|
|
80
|
+
None: [],
|
|
81
|
+
>;
|
|
82
|
+
// Expands to:
|
|
83
|
+
// export type Option<T> =
|
|
84
|
+
// | { readonly _tag: "Some"; readonly _0: T }
|
|
85
|
+
// | { readonly _tag: "None" }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
<div align="right">
|
|
89
|
+
<p><strong>What is a <i>discriminated union</i>?</strong></p>
|
|
90
|
+
<p>These are types that can represent one of several variants, and you can determine which variant it is by looking at a special property called a <strong>discriminant</strong> (in this case, <code>_tag</code>).</p>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
You can create **constructors**, **deconstructors** and **type guards** using the **HKT** (**H**igher-**K**inded **T**ype) of your ADT.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { make } from "kind-adt";
|
|
97
|
+
import { println } from "kind-adt/utils";
|
|
98
|
+
import type { Arg0, HKT } from "hkt-core";
|
|
99
|
+
|
|
100
|
+
export const { Some, None, match: matchOption, isSome, ifSome /* ... */ } = make<OptionHKT>();
|
|
101
|
+
interface OptionHKT extends HKT {
|
|
102
|
+
return: Option<Arg0<this>>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
Some(42); // => { _tag: "Some", _0: 42 }
|
|
106
|
+
// ^?: <number>(args_0: number) => Option<number>
|
|
107
|
+
|
|
108
|
+
// Use `println` to print the ADT in a readable format
|
|
109
|
+
println(Some(42)); // Some(42)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
<details>
|
|
113
|
+
<summary>➡️ You can use <code>make</code> with <strong>non-generic ADTs</strong>, which doesn’t require an HKT.</summary>
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
export type IpAddr = Data<{
|
|
117
|
+
V4: [number, number, number, number];
|
|
118
|
+
V6: [string];
|
|
119
|
+
}>;
|
|
120
|
+
|
|
121
|
+
export const IpAddr = make<IpAddr>();
|
|
122
|
+
|
|
123
|
+
IpAddr.V4(127, 0, 0, 1); // => { _tag: "V4", _0: 127, _1: 0, _2: 0, _3: 1 }
|
|
124
|
+
IpAddr.V6("::1"); // => { _tag: "V6", _0: "::1" }
|
|
125
|
+
|
|
126
|
+
println(IpAddr.V4(127, 0, 0, 1)); // V4(127, 0, 0, 1)
|
|
127
|
+
println(IpAddr.V6("::1")); // V6("::1")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
</details>
|
|
131
|
+
|
|
132
|
+
<details>
|
|
133
|
+
<summary>➡️ Performance note on <code>make</code></summary>
|
|
134
|
+
|
|
135
|
+
`make` uses [proxies](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) to “generate” related functions at runtime, which may introduce some performance overhead. If you are concerned about performance, or is developing a library that needs to be as fast as possible, you should provide the names of each _variant_ manually, which eliminates the need for proxies.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
export const Option = make<OptionHKT>(["Some", "None"]);
|
|
139
|
+
|
|
140
|
+
// Use it the same way as before
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
</details>
|
|
144
|
+
|
|
145
|
+
<div align="right">
|
|
146
|
+
<p><strong>What is a <i>Higher-Kinded Type</i>?</strong></p>
|
|
147
|
+
<p>You can think of it as a <strong><i>type-level function</i></strong> that takes one or more types and returns a type. In this case, <code>OptionHKT</code> is a type-level function that takes one type (<code>Arg0<this></code>) and returns a type (<code>Option<Arg0<this>></code>), which can be represented as <code>Type -> Type</code>.</p>
|
|
148
|
+
<p>kind-adt itself does not export an HKT implementation, but it accepts any HKT implementation that satisfies the <strong><a href="https://github.com/Snowflyt/hkt-core">hkt-core</a></strong> V1 standard. You can directly use the one exported from <a href="https://github.com/Snowflyt/hkt-core">hkt-core</a>, or <a href="#i-dont-want-to-install-another-package-for-hkt-can-i-implement-my-own">implement your own</a> — if neither suits your needs, just creating constructors manually is also an option.</p>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
With the magic of HKT, these generated constructors are already **_generic_** and **_type-safe_**.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// The return type is inferred as `Option<number>`
|
|
155
|
+
function safeDivide(n: number, d: number) {
|
|
156
|
+
// ^?: (n: number, d: number) => Option<number>
|
|
157
|
+
if (d === 0) return None;
|
|
158
|
+
return Some(n / d);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
<div align="right">
|
|
163
|
+
<p><strong>I can directly use <code>None</code> as an <code>Option</code> <i>without calling it with any arguments</i>?</strong></p>
|
|
164
|
+
<p>Right! Constructors themselves have a <code>_tag</code> property that is the discriminant of the ADT, so if a constructor has no arguments, it is <strong>already a valid <i>variant</i></strong> of the ADT.</p>
|
|
165
|
+
<p>While <code>None</code> is a valid <code>Option</code>, you can still call it with a generic argument to change the return type in TypeScript, e.g., <code>None<string>()</code> will return an <code>Option<string></code>.</p>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
Once you have your ADT, you can **pattern match** on it with the generated `match` function!
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
function getOrElse<T>(opt: Option<T>, defaultValue: T): T {
|
|
172
|
+
return matchOption(opt, {
|
|
173
|
+
Some: (value) => value,
|
|
174
|
+
None: () => defaultValue,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
<div align="right">
|
|
180
|
+
<p><strong>It’s <i>not</i> real pattern matching, actually.</strong></p>
|
|
181
|
+
<p>That’s right. Pattern matching normally includes support for nested patterns, guards, and more, but kind-adt only provides a simple pattern matching function that is more like a <strong>switch</strong> statement in TypeScript. Also check out kind-adt’s sister project <a href="https://github.com/Snowflyt/megamatch">megamatch</a> for a more powerful pattern matching library that has built-in support for kind-adt style ADTs.</p>
|
|
182
|
+
<p><strong>Sometimes I only want to match <i>a single variant</i> of an ADT, any syntax like <code>if let</code> in Rust?</strong></p>
|
|
183
|
+
<p>As shown in the example, <code>make<OptionHKT>()</code> also generates <code>ifSome</code> and <code>ifNone</code>. While not mentioned in this quickstart guide, <code>make</code> generates many more helper functions than just constructors and matchers, including <code>if*</code>, <code>is*</code>, <code>unwrap*</code>, and more. Check the <a href="#type-guards-and-unwrap">type guards section</a> and the <a href="#conditional-deconstructors-if">conditional deconstructors section</a> for more details.</p>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
`match` also provides a curried overload that can be useful when combined with the `pipe` utility from libraries like [Effect](https://github.com/Effect-TS/effect) or [fp-ts](https://github.com/gcanti/fp-ts).
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { pipe } from "effect";
|
|
190
|
+
|
|
191
|
+
pipe(
|
|
192
|
+
Some(42),
|
|
193
|
+
matchOption({
|
|
194
|
+
Some: (n) => n * 2,
|
|
195
|
+
None: () => 0,
|
|
196
|
+
}),
|
|
197
|
+
); // => 84
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Note that `ADT.match` requires the return type of each case to be the same. To allow different return types, you can use the `ADT.matchW` function, where the `W` suffix stands for _wider_. `ADT.matchW` can be used the same way as `ADT.match`, supporting both curried and non-curried overloads.
|
|
201
|
+
|
|
202
|
+
<div align="right">
|
|
203
|
+
<p><strong>What if I want to handle multiple variants of an ADT in a single case?</strong></p>
|
|
204
|
+
<p>kind-adt doesn’t support this feature directly, but allows you to use a “catch-all” case to handle the remaining variants of an ADT. (while our sister project <a href="https://github.com/Snowflyt/megamatch">megamatch</a> does support this feature, with built-in support for kind-adt style ADTs)</p>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
`ADT.match(W)` performs exhaustiveness checking that requires you to handle all variants of an ADT. If you want to handle only some variants of an ADT and leave the rest to the default case, you can use a “catch-all” case with a `_` wildcard:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
matchOption(Some(42), {
|
|
211
|
+
Some: (n) => n * 2,
|
|
212
|
+
_: () => 0, // Catch-all case
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
<div align="right">
|
|
217
|
+
<p><strong>What’s next?</strong></p>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
- The type signatures of generated functions are very readable, but you can [improve the readability of the type signatures with labeled tuples](#provide-more-readable-type-signatures).
|
|
221
|
+
- Check out the [syntax sugar for ADTs with only one object field](#syntax-sugar-for-adts-with-only-one-object-field) and [recursive ADTs](#recursive-adts).
|
|
222
|
+
- See how to [check the type of an ADT with **type guards** and extract the fields with **`unwrap`**](#type-guards-and-unwrap).
|
|
223
|
+
- Check out the [conditional deconstructors](#conditional-deconstructors-if) if you are tired with using `match` on a single variant with a verbose catch-all case.
|
|
224
|
+
- See how to use [optional utilities](#optional-utilities) like `println` and `show` to debug your ADTs with ease.
|
|
225
|
+
|
|
226
|
+
## Recipes
|
|
227
|
+
|
|
228
|
+
### Extract variant types
|
|
229
|
+
|
|
230
|
+
You can extract the type of each variant of an ADT using the `Tagged` utility type.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import type { Data, Tagged } from "kind-adt";
|
|
234
|
+
|
|
235
|
+
type Option<T> = Data<
|
|
236
|
+
Some: [T],
|
|
237
|
+
None: [],
|
|
238
|
+
>;
|
|
239
|
+
|
|
240
|
+
type Some<T> = Extract<Option<T>, Tagged<"Some">>;
|
|
241
|
+
// Expands to:
|
|
242
|
+
// type Some<T> = {
|
|
243
|
+
// readonly _tag: "Some";
|
|
244
|
+
// readonly _0: T;
|
|
245
|
+
// }
|
|
246
|
+
type None = Extract<Option<unknown>, Tagged<"None">>;
|
|
247
|
+
// Expands to:
|
|
248
|
+
// type None = {
|
|
249
|
+
// readonly _tag: "None"
|
|
250
|
+
// }
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Provide more readable type signatures
|
|
254
|
+
|
|
255
|
+
Let’s revisit the `Option<T>` example in the quickstart section.
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
type Option<T> = Data<
|
|
259
|
+
Some: [T],
|
|
260
|
+
None: [],
|
|
261
|
+
>;
|
|
262
|
+
|
|
263
|
+
const Option = make<OptionHKT>();
|
|
264
|
+
interface OptionHKT extends HKT {
|
|
265
|
+
return: Option<Arg0<this>>;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
Option.Some(42);
|
|
269
|
+
// ^?: <number>(args_0: number) => Option<number>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
In this case, the generated constructor `Some` has a type signature of `<T>(args_0: T) => Option<T>` instead of the more readable `<T>(value: T) => Option<T>`. This naming issue also affects other functions like `Option.match`, `Option.unwrap`, etc. We can improve the readability of these type signatures by using TypeScript [_labeled tuples_](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#labeled-tuple-elements).
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
type Option<T> = Data<
|
|
276
|
+
Some: [value: T],
|
|
277
|
+
None: [],
|
|
278
|
+
>;
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
And that’s it! Now all generated functions will have a more readable type signature.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
function Option.Some<T>(value: T): Option<T>;
|
|
285
|
+
|
|
286
|
+
function Option.match<T, R>(adt: Option<T>, cases: {
|
|
287
|
+
readonly Some: (value: T) => R;
|
|
288
|
+
readonly None: () => R;
|
|
289
|
+
}): R;
|
|
290
|
+
|
|
291
|
+
// ...
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
This also applies to other ADTs, such as `Result`, `Either`, etc.
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
type Result<T, E> = Data<
|
|
298
|
+
Ok: [value: T],
|
|
299
|
+
Err: [error: E],
|
|
300
|
+
>;
|
|
301
|
+
|
|
302
|
+
type Either<A, B> = Data<
|
|
303
|
+
Left: [value: A],
|
|
304
|
+
Right: [value: B],
|
|
305
|
+
>;
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Syntax sugar for ADTs with only one object field
|
|
309
|
+
|
|
310
|
+
If your ADT has only one object field, declaring it like this would be a little tedious:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
type Tree<T> = Data<{
|
|
314
|
+
Empty: [];
|
|
315
|
+
Node: [{ value: T; left: Tree<T>; right: Tree<T> }];
|
|
316
|
+
}>;
|
|
317
|
+
// Expands to:
|
|
318
|
+
// type Tree<T> =
|
|
319
|
+
// | { readonly _tag: "Empty" }
|
|
320
|
+
// | { readonly _tag: "Node"; readonly _0: { value: T; left: Tree<T>; right: Tree<T> } }
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
To make it easier to declare, kind-adt provides syntax sugar for this case. You can declare an ADT with a bare object literal type instead of a tuple literal type. This serves as a shorthand for a tuple type with a single object field.
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
type Tree<T> = Data<{
|
|
327
|
+
Empty: {}; // In this case, `{}` is equivalent to `[]`
|
|
328
|
+
Node: { value: T; left: Tree<T>; right: Tree<T> };
|
|
329
|
+
}>;
|
|
330
|
+
// Expands to:
|
|
331
|
+
// type Tree<T> =
|
|
332
|
+
// | { readonly _tag: "Empty" }
|
|
333
|
+
// | { readonly _tag: "Node"; readonly _0: { value: T; left: Tree<T>; right: Tree<T> } }
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Then you can use it like this:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
interface TreeHKT extends HKT {
|
|
340
|
+
return: Tree<Arg0<this>>;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const Tree = make<TreeHKT>();
|
|
344
|
+
|
|
345
|
+
const depth: <T>(tree: Tree<T>) => number = Tree.match({
|
|
346
|
+
Empty: () => 0,
|
|
347
|
+
Node: ({ left, right }) => 1 + Math.max(depth(left), depth(right)),
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Recursive ADTs
|
|
352
|
+
|
|
353
|
+
The `Tree<T>` example above is already a recursive ADT — TypeScript naturally supports recursive types when they’re defined using object literal types.
|
|
354
|
+
|
|
355
|
+
However, things get a little tricky when you want to declare the `Node` variant with 3 fields (`value`, `left`, and `right`) directly instead of using an object as the only field:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
type Tree<T> = Data<{
|
|
359
|
+
// ~~~~
|
|
360
|
+
// Type alias 'Tree' circularly references itself. ts(2456)
|
|
361
|
+
Empty: [];
|
|
362
|
+
Node: [value: T, left: Tree<T>, right: Tree<T>];
|
|
363
|
+
}>;
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
This type error originates from the internal evaluation mechanism of TypeScript: types like interfaces, object literal types and function return types are “lazily” evaluated (evaluated only when necessary), while type aliases are “eagerly” evaluated (evaluated immediately). See [this Stack Overflow answer](https://stackoverflow.com/questions/37233735/interfaces-vs-types-in-typescript/77669722#77669722) for more details.
|
|
367
|
+
|
|
368
|
+
In our scenario, this means that when TypeScript tries to evaluate the `Tree<T>` type alias, it will eagerly evaluate the `Node` variant, which references `Tree<T>` itself, causing a circular reference error.
|
|
369
|
+
|
|
370
|
+
To avoid this, kind-adt provides an alternative syntax to declare recursive ADTs like this, where an object literal type with numeric keys is used as an alternative to a tuple type:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
type Tree<T> = Data<{
|
|
374
|
+
Empty: [];
|
|
375
|
+
Node: { 0: T; 1: Tree<T>; 2: Tree<T> };
|
|
376
|
+
}>;
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
<details>
|
|
380
|
+
<summary>➡️ Click to see how to add labels to the fields of a recursive ADT</summary>
|
|
381
|
+
|
|
382
|
+
To add labels to the fields (see [Provide more readable type signatures](#provide-more-readable-type-signatures)), you can use a magical field `__labels` that is a labeled tuple exists only in the type system:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
type Tree<T> = Data<{
|
|
386
|
+
Empty: [];
|
|
387
|
+
Node: {
|
|
388
|
+
__labels: [value: void, left: void, right: void];
|
|
389
|
+
0: T;
|
|
390
|
+
1: Tree<T>;
|
|
391
|
+
2: Tree<T>;
|
|
392
|
+
};
|
|
393
|
+
}>;
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
The type of each element in `__labels` does not matter, only the labels are used to provide better type signatures for the generated functions.
|
|
397
|
+
|
|
398
|
+
</details>
|
|
399
|
+
|
|
400
|
+
Then you can use it like this:
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
interface TreeHKT extends HKT {
|
|
404
|
+
return: Tree<Arg0<this>>;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const Tree = make<TreeHKT>();
|
|
408
|
+
|
|
409
|
+
const depth: <T>(tree: Tree<T>) => number = Tree.match({
|
|
410
|
+
Empty: () => 0,
|
|
411
|
+
Node: (_, left, right) => 1 + Math.max(depth(left), depth(right)),
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Type guards and `unwrap`
|
|
416
|
+
|
|
417
|
+
While `ADT.match(W)` is a powerful tool for handling ADTs, sometimes you simply need to check if an ADT is a specific variant and extract its value. For these cases, kind-adt provides `ADT.is*` and `ADT.unwrap*` functions.
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
const Option = make<OptionHKT>();
|
|
421
|
+
|
|
422
|
+
function getOrElse<T>(opt: Option<T>, defaultValue: T): T {
|
|
423
|
+
if (Option.isSome(opt)) return opt._0;
|
|
424
|
+
return defaultValue;
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
However, accessing the fields of an ADT with `._${number}` like this is not very readable, so kind-adt also provides `ADT.unwrap*` functions to extract the fields of an ADT into a tuple:
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
function getOrElse<T>(opt: Option<T>, defaultValue: T): T {
|
|
432
|
+
if (Option.isSome(opt)) {
|
|
433
|
+
const [value] = Option.unwrap(opt);
|
|
434
|
+
return value;
|
|
435
|
+
}
|
|
436
|
+
return defaultValue;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function depth<T>(tree: Tree<T>): number {
|
|
440
|
+
if (Tree.isEmpty(tree)) return 0;
|
|
441
|
+
const [_, left, right] = Tree.unwrap(tree);
|
|
442
|
+
return 1 + Math.max(depth(left), depth(right));
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
You can also use `ADT.unwrap*` like `Option.unwrapSome` to extract the value of a specific variant of an ADT, which will throw a runtime error if the ADT is not of that variant.
|
|
447
|
+
|
|
448
|
+
A standalone `unwrap` function is exported directly from kind-adt, which can be useful if you want to handle any ADT without knowing its type at compile time.
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
import { unwrap } from "kind-adt";
|
|
452
|
+
|
|
453
|
+
const [value] = unwrap(Some(42));
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Conditional deconstructors (`if*`)
|
|
457
|
+
|
|
458
|
+
You might often need to write code like this:
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
Option.match(safeDivide(42, 2), {
|
|
462
|
+
Some: (n) => console.log("This is a very long message that I want to log", n),
|
|
463
|
+
_ => {},
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Since the `match` function performs exhaustiveness checking, you have to provide a catch-all case `_` to handle the remaining variants of the ADT. This can be awkward and a waste of space when your codebase is full of such cases.
|
|
468
|
+
|
|
469
|
+
kind-adt provides `ADT.if*` functions to handle this case more elegantly, similar to [the `if let` syntax in Rust](https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html):
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
Option.ifSome(safeDivide(42, 2), (n) => {
|
|
473
|
+
console.log("This is a very long message that I want to log", n);
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
If the ADT matches the specified variant, the callback function will be called with the value of that variant, otherwise nothing will happen.
|
|
478
|
+
|
|
479
|
+
The `if*` function also has a return type: if the matching succeeds, the return type will be the return type of the callback function, otherwise it will be `void` (`undefined` in JavaScript).
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
const result = Option.ifSome(safeDivide(42, 2), (n) => {
|
|
483
|
+
// ^?: number | void
|
|
484
|
+
console.log("This is a very long message that I want to log", n);
|
|
485
|
+
return n;
|
|
486
|
+
});
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
You can also provide an optional second argument to the `if*` function, which is a callback function that will be called if the matching fails. If it is provided, the return type of the `if*` function will be the return type of either the first or the second callback function, depending on whether the matching succeeds or fails.
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
const result = Option.ifSome(
|
|
493
|
+
// ^?: number | string
|
|
494
|
+
safeDivide(42, 2),
|
|
495
|
+
(n) => {
|
|
496
|
+
console.log("This is a very long message that I want to log", n);
|
|
497
|
+
return n;
|
|
498
|
+
},
|
|
499
|
+
() => {
|
|
500
|
+
console.log("This is a very long message that I want to log");
|
|
501
|
+
return "default value";
|
|
502
|
+
},
|
|
503
|
+
);
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## Optional Utilities
|
|
507
|
+
|
|
508
|
+
> [!NOTE]
|
|
509
|
+
>
|
|
510
|
+
> To use the `println` and `show` utilities, the [showify](https://github.com/Snowflyt/showify) package is required to be installed.
|
|
511
|
+
|
|
512
|
+
### `println`
|
|
513
|
+
|
|
514
|
+
You can use the `println` function to print an ADT in a readable format, which is especially useful for debugging.
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
import { println } from "kind-adt/utils";
|
|
518
|
+
|
|
519
|
+
// Suppose we have an ADT `data Tree<T> = Empty | Node(T, Tree<T>, Tree<T>)`
|
|
520
|
+
const tree = Tree.Node(
|
|
521
|
+
1,
|
|
522
|
+
Tree.Node(2, Tree.Node(3, Tree.Empty, Tree.Empty), Tree.Empty),
|
|
523
|
+
Tree.Node(4, Tree.Empty, Tree.Node(3, Tree.Empty, Tree.Empty)),
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// ANSI colors are supported
|
|
527
|
+
println(tree);
|
|
528
|
+
// Node(
|
|
529
|
+
// 1,
|
|
530
|
+
// Node(2, Node(3, Empty, Empty), Empty),
|
|
531
|
+
// Node(4, Empty, Node(3, Empty, Empty))
|
|
532
|
+
// )
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Check out [`show`](#show) if you want more control over the output.
|
|
536
|
+
|
|
537
|
+
### `show`
|
|
538
|
+
|
|
539
|
+
The `println` utility uses the [showify](https://github.com/Snowflyt/showify) package to convert an ADT into a string. Since `println` accepts variadic arguments, it does not support passing options to control the output. If you want more control over the output, you can use the `show` function to convert an ADT into a string, which accepts an optional `ShowOptions` object from the showify package to control the output.
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
import { show } from "kind-adt/utils";
|
|
543
|
+
|
|
544
|
+
// Suppose we have an ADT `data Tree<T> = Empty | Node(T, Tree<T>, Tree<T>)`
|
|
545
|
+
const tree = Tree.Node(1, Tree.Node(2, Tree.Empty, Tree.Empty), Tree.Empty);
|
|
546
|
+
|
|
547
|
+
console.log(show(tree));
|
|
548
|
+
// Node(1, Node(2, Empty, Empty), Empty)
|
|
549
|
+
|
|
550
|
+
console.log(show(tree, { indent: 4, breakLength: 0, trailingComma: "auto" }));
|
|
551
|
+
// Node(
|
|
552
|
+
// 1,
|
|
553
|
+
// Node(
|
|
554
|
+
// 2,
|
|
555
|
+
// Empty,
|
|
556
|
+
// Empty,
|
|
557
|
+
// ),
|
|
558
|
+
// Empty,
|
|
559
|
+
// )
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## FAQ
|
|
563
|
+
|
|
564
|
+
### I don’t want to install another package for HKT. Can I implement my own?
|
|
565
|
+
|
|
566
|
+
The [**hkt-core**](https://github.com/Snowflyt/hkt-core) V1 standard is simple enough to implement by yourself. By extending `HKT` exported from **hkt-core**, you only get two additional properties (`~hkt` and `signature`), which can be easily defined manually.
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
type Args<F> = F extends { Args: (_: infer A extends unknown[]) => void } ? A : never;
|
|
570
|
+
|
|
571
|
+
// No need to extend `HKT` ↙
|
|
572
|
+
interface OptionHKT {
|
|
573
|
+
// ↙ Required by the standard
|
|
574
|
+
"~hkt": { version: 1 };
|
|
575
|
+
// ↓ This defines the type signature (kind) of the HKT
|
|
576
|
+
signature: (type: unknown) => Option<unknown>;
|
|
577
|
+
// You can use types other than `unknown` if your type parameters are constrained,
|
|
578
|
+
// to provide better type safety.
|
|
579
|
+
// signature: (type: string | number) => Option<string | number>
|
|
580
|
+
// ↓ The same as before
|
|
581
|
+
return: Option<Args<this>[0]>;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// No need to extend `HKT2` ↙
|
|
585
|
+
interface ResultHKT {
|
|
586
|
+
"~hkt": { version: 1 };
|
|
587
|
+
// ↓ Since this HKT has two type parameters, the signature should accept two arguments
|
|
588
|
+
signature: (type1: unknown, type2: unknown) => Result<unknown, unknown>;
|
|
589
|
+
return: Result<Args<this>[0], Args<this>[1]>;
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
You can also define your own `HKT` and `HKT2` types if you don’t want to specify all these properties manually every time.
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
interface HKT<Type = unknown> {
|
|
597
|
+
"~hkt": { version: 1 };
|
|
598
|
+
signature: (type: Type) => unknown;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
interface HKT2<Type1 = unknown, Type2 = unknown> {
|
|
602
|
+
"~hkt": { version: 1 };
|
|
603
|
+
signature: (type1: Type1, type2: Type2) => unknown;
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Why “Kind”?
|
|
608
|
+
|
|
609
|
+
<strong><i>Kind</i></strong> is [a term used in type theory](<https://en.wikipedia.org/wiki/Kind_(type_theory)>) to describe the “type of a type”, or the “type of a type constructor (i.e. HKT)”. For example, the kind of `number`, `Option<string>` and `Result<number, string>` are all `Type`, while the kind of `OptionHKT` is `Type -> Type`, and the kind of `ResultHKT` is `(Type, Type) -> Type`.
|
|
610
|
+
|
|
611
|
+
The name kind-adt is a play on words, combining the term “kind” with “ADT” to emphasize the usage of HKTs in defining ADTs.
|
|
612
|
+
|
|
613
|
+
### ADTs in kind-adt are _incompatible_ with those in Effect or fp-ts!
|
|
614
|
+
|
|
615
|
+
That’s true — instead of using `"_${number}"` as field names, these libraries use a more descriptive field name like `"value"` or `"error"`. The design of not following this convention in kind-adt is intentional to allow a cleaner way to match multiple fields in `match` without the need for object destructuring. The use of unreadable field names also encourage users to use `match` instead of directly accessing fields.
|
|
616
|
+
|
|
617
|
+
Check [ts-adt](https://github.com/pfgray/ts-adt) if you want to use a more compatible ADT library with Effect or fp-ts.
|
|
618
|
+
|
|
619
|
+
## License
|
|
620
|
+
|
|
621
|
+
This project is licensed under the Mozilla Public License Version 2.0 (MPL 2.0).
|
|
622
|
+
For details, please refer to the `LICENSE` file.
|
|
623
|
+
|
|
624
|
+
In addition to the open-source license, a commercial license is available for proprietary use.
|
|
625
|
+
If you modify this library and do not wish to open-source your modifications, or if you wish to use the modified library as part of a closed-source or proprietary project, you must obtain a commercial license.
|
|
626
|
+
|
|
627
|
+
For details, see `COMMERCIAL_LICENSE.md`.
|