forked from baetheus/fun
-
Notifications
You must be signed in to change notification settings - Fork 0
/
newtype.ts
230 lines (217 loc) · 5.74 KB
/
newtype.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/**
* Newtype presents a type level "rebranding" of an existing type.
*
* It's basic purpose is to create a branded type from an existing
* type. This is much like using TypeScript type aliases, ie.
* `type MyNumber = number`. However, Newtype will prevent the
* existing type from being used where the Branded type is
* specified.
*
* @module Newtype
*/
import type { Monoid } from "./monoid.ts";
import type { Ord } from "./ord.ts";
import type { Semigroup } from "./semigroup.ts";
import type { Eq } from "./eq.ts";
import type { Predicate } from "./predicate.ts";
import type { Iso, Prism } from "./optics.ts";
import type { Option } from "./option.ts";
import { fromPredicate } from "./option.ts";
import { iso as _iso, prism as _prism } from "./optics.ts";
import { identity, unsafeCoerce } from "./fn.ts";
/**
* These are phantom types used by Newtype to both identify and distinquish
* between a Newtype and its representation value.
*/
declare const Brand: unique symbol;
declare const Value: unique symbol;
/**
* Create a branded type from an existing type. The branded
* type can be used anywhere the existing type can, but the
* existing type cannot be used where the branded one can.
*
* It is important to note that while a Newtype looks like
* a struct, its actual representation is that of the
* inner type.
*
* @example
* ```ts
* import type { Newtype } from "./newtype.ts";
*
* type Integer = Newtype<'Integer', number>;
*
* const int = 1 as unknown as Integer;
* const num = 1;
*
* declare function addOne(n: Integer): number;
*
* addOne(int); // This is ok!
* // addOne(num); // This is not
* ```
*
* @since 2.0.0
*/
export type Newtype<B, A> = { readonly [Brand]: B; readonly [Value]: A };
/**
* Extracts the inner type value from a Newtype.
*
* @example
* ```ts
* import type { Newtype, ToValue } from "./newtype.ts";
*
* type Integer = Newtype<'Integer', number>;
*
* type InnerInteger = ToValue<Integer>; // number
* ```
*
* @since 2.0.0
*/
export type ToValue<T extends Newtype<unknown, unknown>> = T[typeof Value];
/**
* A type alias for Newtype<any, any> that is useful when constructing
* Newtype related runtime instances.
*
* @since 2.0.0
*/
export type AnyNewtype = Newtype<unknown, unknown>;
/**
* Retype an existing Eq from an inner type to a Newtype.
*
* @example
* ```ts
* import { Newtype, getEq } from "./newtype.ts";
* import * as N from "./number.ts";
*
* type Integer = Newtype<'Integer', number>;
*
* const eqInteger = getEq<Integer>(N.EqNumber);
* ```
*
* @since 2.0.0
*/
export function getEq<T extends AnyNewtype>(
eq: Eq<ToValue<T>>,
): Eq<T> {
return eq as Eq<T>;
}
/**
* Retype an existing Ord from an inner type to a Newtype.
*
* @example
* ```ts
* import { Newtype, getOrd } from "./newtype.ts";
* import * as N from "./number.ts";
*
* type Integer = Newtype<'Integer', number>;
*
* const ordInteger = getOrd<Integer>(N.OrdNumber);
* ```
*
* @since 2.0.0
*/
export function getOrd<T extends AnyNewtype>(ord: Ord<ToValue<T>>): Ord<T> {
return ord as Ord<T>;
}
/**
* Retype an existing Semigroup from an inner type to a Newtype.
*
* @example
* ```ts
* import { Newtype, getSemigroup } from "./newtype.ts";
* import * as N from "./number.ts";
*
* type Integer = Newtype<'Integer', number>;
*
* const semigroupInteger = getSemigroup<Integer>(N.SemigroupNumberSum);
* ```
*
* @since 2.0.0
*/
export function getSemigroup<T extends AnyNewtype>(
semigroup: Semigroup<ToValue<T>>,
): Semigroup<T> {
return semigroup as unknown as Semigroup<T>;
}
/**
* Retype an existing Monoid from an inner type to a Newtype.
*
* @example
* ```ts
* import { Newtype, getMonoid } from "./newtype.ts";
* import * as N from "./number.ts";
*
* type Integer = Newtype<'Integer', number>;
*
* const monoidInteger = getMonoid<Integer>(N.MonoidNumberSum);
* ```
*
* @since 2.0.0
*/
export function getMonoid<T extends AnyNewtype>(
monoid: Monoid<ToValue<T>>,
): Monoid<T> {
return monoid as unknown as Monoid<T>;
}
// deno-lint-ignore no-explicit-any
const _anyIso: Iso<any, any> = _iso(unsafeCoerce, unsafeCoerce);
/**
* If the Newtype and its underlying value are referentially transparent
* (meaning they can always be swapped) then you can create an instance of Iso
* for the Newtype for mapping back and forth.
*
* @example
* ```ts
* import * as N from "./newtype.ts";
*
* type Real = N.Newtype<"Real", number>;
*
* const isoReal = N.iso<Real>();
*
* const result1: Real = isoReal.view(1); // Turn number into Real
* const result2: number = isoReal.review(result1); // Turn Real into number
* ```
*
* @since 2.0.0
*/
export function iso<T extends AnyNewtype>(): Iso<ToValue<T>, T> {
return _anyIso;
}
/**
* If the Newtype and its underlying value are not referentially transparent
* (meaning they can always be swapped) then you can create an instance of Prism
* for the Newtype in order to optionally map into the Newtype given some
* Predicate.
*
* @example
* ```ts
* import type { Option } from "./option.ts";
*
* import * as N from "./newtype.ts";
* import * as O from "./option.ts";
* import { pipe } from "./fn.ts";
*
* type Integer = N.Newtype<"Integer", number>;
*
* const prismInteger = N.prism<Integer>(Number.isInteger);
*
* // Turn number into an Option<Integer>
* const result1: Option<Integer> = prismInteger.view(1);
*
* // Turn an Option<Integer> into a number or fall back to 0 if Option is None
* const result2: number = pipe(
* result1,
* O.map(prismInteger.review),
* O.getOrElse(() => 0)
* );
* ```
*
* @since 2.0.0
*/
export function prism<T extends AnyNewtype>(
predicate: Predicate<ToValue<T>>,
): Prism<ToValue<T>, T> {
return _prism<ToValue<T>, T>(
fromPredicate(predicate) as (s: ToValue<T>) => Option<T>,
identity,
);
}