-
Notifications
You must be signed in to change notification settings - Fork 7
/
initializable.ts
175 lines (165 loc) · 4.58 KB
/
initializable.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
/**
* This file contains the type and utilities for the Initializable algebraic
* data structure. A type that implements Initializable has a concept of "empty"
* represented by the init method and a concept to combine represented by the
* combine method. In other functional libraries and languages Initializable is
* called Monoid.
*
* @module Initializable
* @since 2.0.0
*/
import type { Combinable } from "./combinable.ts";
import type { AnyReadonlyRecord } from "./record.ts";
import * as C from "./combinable.ts";
/**
* A Initializable structure has the method init.
*
* @since 2.0.0
*/
export interface Initializable<A> extends Combinable<A> {
readonly init: () => A;
}
/**
* A type for Initializable over any fixed value, useful as an extension target
* for functions that take any Initializable and do not need to unwrap the
* type.
*
* @since 2.0.0
*/
// deno-lint-ignore no-explicit-any
export type AnyInitializable = Initializable<any>;
/**
* A type level unwrapor, used to pull the inner type from a Combinable.
*
* @since 2.0.0
*/
export type TypeOf<T> = T extends Initializable<infer A> ? A : never;
/**
* Create an Initializable fixed to a concrete value A. This operates like init
* from Combinable in other functional libraries.
*
* @since 2.0.0
*/
export function constant<A>(value: A): Initializable<A> {
return { init: () => value, combine: () => () => value };
}
/**
* Create an Initializable fixed to a struct using nested Initializables to
* create the init values within.
*
* @since 2.0.0
*/
export function struct<O extends AnyReadonlyRecord>(
initializables: { [K in keyof O]: Initializable<O[K]> },
): Initializable<{ readonly [K in keyof O]: O[K] }> {
type Entries = [keyof O, typeof initializables[keyof O]][];
return ({
init: () => {
const r = {} as Record<keyof O, O[keyof O]>;
for (const [key, { init }] of Object.entries(initializables) as Entries) {
r[key] = init();
}
return r as { [K in keyof O]: O[K] };
},
...C.struct(initializables),
});
}
/**
* Create an Initializable fixed to a tuple using nested Initializables to
* create the init values within.
*
* @since 2.0.0
*/
export function tuple<T extends AnyInitializable[]>(
...initializables: T
): Initializable<{ readonly [K in keyof T]: TypeOf<T[K]> }> {
return {
init: () => initializables.map(({ init }) => init()),
...C.tuple(...initializables),
} as Initializable<{ readonly [K in keyof T]: TypeOf<T[K]> }>;
}
/**
* Create a dual Initializable from an existing initializable. This effectively
* switches the order of application of the original Initializable.
*
* @example
* ```ts
* import { dual, getCombineAll, intercalcate } from "./initializable.ts";
* import { InitializableString } from "./string.ts";
* import { pipe } from "./fn.ts";
*
* const reverse = dual(InitializableString);
* const reverseAll = pipe(
* reverse,
* intercalcate(" "),
* getCombineAll,
* );
*
* const result = reverseAll("Hello", "World"); // "World Hello"
* ```
*
* @since 2.0.0
*/
export function dual<A>(M: Initializable<A>): Initializable<A> {
return ({
combine: C.dual(M).combine,
init: M.init,
});
}
/**
* Create a initializable that works like Array.join,
* inserting middle between every two values
* that are combineenated. This can have some interesting
* results.
*
* @example
* ```ts
* import * as M from "./initializable.ts";
* import * as S from "./string.ts";
* import { pipe } from "./fn.ts";
*
* const { combine: toList } = pipe(
* S.InitializableString,
* M.intercalcate(", "),
* );
*
* const list = pipe(
* "apples",
* toList("oranges"),
* toList("and bananas"),
* ); // list === "apples, oranges, and bananas"
* ```
*
* @since 2.0.0
*/
export function intercalcate<A>(
middle: A,
): (I: Initializable<A>) => Initializable<A> {
return ({ combine, init }) => ({
combine: (second) => combine(combine(second)(middle)),
init,
});
}
/**
* Given an Initializable, create a function that will
* iterate through an array of values and combine
* them. This is not much more than Array.fold(combine).
*
* @example
* ```ts
* import * as I from "./initializable.ts";
* import * as N from "./number.ts";
*
* const sumAll = I.getCombineAll(N.InitializableNumberSum);
*
* const result = sumAll(1, 30, 80, 1000, 52, 42); // sum === 1205
* ```
*
* @since 2.0.0
*/
export function getCombineAll<A>(
{ combine, init }: Initializable<A>,
): (...as: ReadonlyArray<A>) => A {
const _combine = (first: A, second: A) => combine(second)(first);
return (...as) => as.reduce(_combine, init());
}