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/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&lt;this&gt;</code>) and returns a type (<code>Option&lt;Arg0&lt;this&gt;&gt;</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&lt;string&gt;()</code> will return an <code>Option&lt;string&gt;</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&lt;OptionHKT&gt;()</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`.