-
Notifications
You must be signed in to change notification settings - Fork 7
/
kind.ts
164 lines (150 loc) · 5.67 KB
/
kind.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* Kind is a collection of types used for doing something called Type
* Substitution. The core idea here is that sometimes there is a function that
* is generic at two levels, at the concrete type as well as at the type leve.
* A simple example of this is a *forEach* function. In javascript the built in
* *Array* and *Set* data structures both have a built in *forEach* method. If
* we look at Array and Set we can see that the creation functions are similar.
*
* * `const arr = new Array<number>()` returns Array<number>;
* * `const set = new Set<number>()` returns Set<number>;
*
* If we look at the type signitures on forEach for `arr` and `set` we also
* notice a similar pattern.
*
* * `type A = (typeof arr)['forEach']` returns `(callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void`
* * `type B = (typeof set)['forEach']` returns `(callbackfn: (value: number, value2: number, set: Set<number>) => void, thisArg?: any) => void`
*
* Both forEach methods have the same concrete value of `number` but differ in
* that they operate over an Array or a Set. Here is a table to illustrate this
* pattern a bit more clearly.
*
* | Structure | Outer Type | Inner Type | forEach Type |
* | --------- | ---------- | ------------------ | ------------------------------------------------- |
* | Array<A> | Array | A | (value: A, index: number, struct: A[]) => void |
* | Set<A> | Set | A | (value: A, index: number, struct: Set<A>) => void |
* | Map<K, V> | Map | K (key), V (value) | (value: V, key: K, struct: Map<K, V>) => void |
*
* In general we can see that the forEach function could have a more generic
* type signiture like this:
*
* ```
* type ForEach<Outer> = {
* forEach: <K, V>(struct: Outer<K, V>) => (value: V, key: K, struct: Outer<K, V>) => void;
* }
* ```
*
* Unfortunately, while these types of patterns are abundant within TypeScript
* (and most programming languages). The ability to pass generic types around
* and fill type holes is not built into TypeScript. However, with some type
* magic we can create our own type level substitutions using methods pionered
* by [gcanti](https://github.com/gcanti) in *fp-ts*.
*
* @module Kind
* @since 2.0.0
*/
/**
* The Substitutions type splits the different type level substitutions into
* tuples based on variance.
*/
export type Substitutions = {
// Covariant Substitutions: () => A
readonly ["out"]: unknown[];
// Premappable Substitutions: (A) => void
readonly ["in"]: unknown[];
// Invariant Substitutions: (A) => A
readonly ["inout"]: unknown[];
};
/**
* Kind is an interface that can be extended
* to retrieve inner types using "this".
*/
export interface Kind extends Substitutions {
readonly kind?: unknown;
}
/**
* Substitute is a substitution type, taking a Kind implementation T and
* substituting it with types passed in S.
*/
export type Substitute<T extends Kind, S extends Substitutions> = T extends
{ readonly kind: unknown }
// If the kind property on T is required (no ?) then proceed with substitution
? (T & S)["kind"]
// Otherwise carry T and the substitutions forward, preserving variance.
: {
readonly T: T;
readonly ["out"]: () => S["out"];
readonly ["in"]: (_: S["in"]) => void;
readonly ["inout"]: (_: S["inout"]) => S["inout"];
};
/**
* $ is an alias of Substitute, lifting out, in, and inout
* substitutions to positional type parameters.
*/
export type $<
T extends Kind,
Out extends unknown[],
In extends unknown[] = [never],
InOut extends unknown[] = [never],
> = Substitute<T, { ["out"]: Out; ["in"]: In; ["inout"]: InOut }>;
/**
* Access the Covariant substitution type at index N
*/
export type Out<T extends Substitutions, N extends keyof T["out"]> =
T["out"][N];
/**
* Access the Premappable substitution type at index N
*/
export type In<T extends Substitutions, N extends keyof T["in"]> = T["in"][N];
/**
* Access the Invariant substitution type at index N
*/
export type InOut<T extends Substitutions, N extends keyof T["inout"]> =
T["inout"][N];
/**
* Fix a concrete type as a non-substituting Kind. This allows one to define
* algebraic structures over things like number, string, etc.
*/
export interface Fix<A> extends Kind {
readonly kind: A;
}
/**
* Create a scoped symbol for use with Hold.
*/
const HoldSymbol = Symbol("Hold");
/**
* The Hold interface allows one to trick the typescript compiler into holding
* onto type information that it would otherwise discard. This is useful when
* creating an interface that merges multiple type classes (see Flatmappable).
*/
export interface Hold<A> {
readonly [HoldSymbol]?: A;
}
/**
* Some Flatmappable Transform kinds only have one out type, in those cases we
* use this Nest type to make the transform kind cleaner.
*/
export type Nest<U extends Kind, S extends Substitutions> = $<
U,
[Out<S, 0>, Out<S, 1>, Out<S, 2>],
[In<S, 0>],
[InOut<S, 0>]
>;
/**
* Spread the keys of a struct union into a single struct.
*/
export type Spread<A> = { [K in keyof A]: A[K] } extends infer B ? B : never;
/**
* An extension type to be used to constrain in input to an outer container with
* any concrete types.
*/
// deno-lint-ignore no-explicit-any
export type AnySub<U extends Kind> = $<U, any[], any[], any[]>;
/**
* A type level utility that turns a type union into a type intersection. This
* type is dangerous and can have unexpected results so extra runtime testing is
* necessary when used.
*/
// deno-lint-ignore no-explicit-any
export type Intersect<U> = (U extends any ? (k: U) => void : never) extends
((k: infer I) => void) ? I : never;