-
Notifications
You must be signed in to change notification settings - Fork 7
/
flatmappable.ts
166 lines (159 loc) · 4.37 KB
/
flatmappable.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
/**
* Flatmappable is a structure that allows a function that returns the concrete
* structure to be applied to the value inside of the same type of concrete
* structure. The resultant nested structure is then flattened.
*
* @module Flatmappable
* @since 2.0.0
*/
import type { $, Hold, Kind } from "./kind.ts";
import type { Applicable } from "./applicable.ts";
/**
* A Flatmappable structure.
*/
export interface Flatmappable<
U extends Kind,
> extends Applicable<U>, Hold<U> {
readonly flatmap: <A, I, J = never, K = never, L = unknown, M = unknown>(
fati: (a: A) => $<U, [I, J, K], [L], [M]>,
) => <B = never, C = never, D = unknown>(
ta: $<U, [A, B, C], [D], [M]>,
) => $<U, [I, B | J, C | K], [D & L], [M]>;
}
/**
* The return type for the createTap function on Flatmappable. Useful to reduce
* type inference in documentation.
*
* @since 2.0.0
*/
export type Tap<U extends Kind> = <A>(
fn: (value: A) => void,
) => <B = never, C = never, D = unknown, E = unknown>(
ua: $<U, [A, B, C], [D], [E]>,
) => $<U, [A, B, C], [D], [E]>;
/**
* Create a tap function for a structure with instances of Wrappable and
* Flatmappable. A tap function allows one to break out of the functional
* codeflow. It is generally not advised to use tap for code flow but to
* consider an escape hatch to do things like tracing or logging.
*
* @since 2.0.0
*/
export function createTap<U extends Kind>(
{ wrap, flatmap }: Flatmappable<U>,
): <A>(
fn: (value: A) => void,
) => <B = never, C = never, D = unknown, E = unknown>(
ua: $<U, [A, B, C], [D], [E]>,
) => $<U, [A, B, C], [D], [E]> {
return (fn) =>
flatmap((a) => {
fn(a);
return wrap(a);
});
}
/**
* The return type for the createBind function on Flatmappable. Useful to reduce
* type inference in documentation.
*
* @since 2.0.0
*/
export type Bind<U extends Kind> = <
N extends string,
A,
I,
J = never,
K = never,
L = unknown,
M = unknown,
>(
name: Exclude<N, keyof A>,
faui: (a: A) => $<U, [I, J, K], [L], [M]>,
) => <B = never, C = never, D extends L = L>(
ua: $<U, [A, B, C], [D], [M]>,
) => $<
U,
[{ readonly [K in keyof A | N]: K extends keyof A ? A[K] : I }, B | J, C | K],
[D & L],
[M]
>;
/**
* Create a bind function for a structure with instances of Mappable and
* Flatmappable. A bind function allows one to flatmap into named fields in a
* struct, collecting values from the result of the flatmap in the order that
* they are completed.
*
* @since 2.0.0
*/
export function createBind<U extends Kind>({ flatmap, map }: Flatmappable<U>): <
N extends string,
A,
I,
J = never,
K = never,
L = unknown,
M = unknown,
>(
name: Exclude<N, keyof A>,
faui: (a: A) => $<U, [I, J, K], [L], [M]>,
) => <B = never, C = never, D extends L = L>(
ua: $<U, [A, B, C], [D], [M]>,
) => $<
U,
[{ readonly [K in keyof A | N]: K extends keyof A ? A[K] : I }, B | J, C | K],
[D & L],
[M]
> {
return (name, faui) =>
flatmap((a) =>
// deno-lint-ignore no-explicit-any
map((i) => Object.assign({}, a, { [name]: i }) as any)(faui(a))
);
}
/**
* Derive a Flatmappable instance from unwrap, flatmap, and a Kind.
* This is the simplest way to get a Flatmappable instance.
*
* @example
* ```ts
* import type { Kind, Out } from "./kind.ts";
* import { createFlatmappable } from "./flatmappable.ts";
* import { pipe } from "./fn.ts";
*
* // Create a Kind for Promise<A>
* interface KindPromise extends Kind {
* readonly kind: Promise<Out<this, 0>>;
* };
*
* // Create an of and chain function for Promise<A>
* const wrap = <A>(a: A): Promise<A> => Promise.resolve(a);
* const flatmap = <A, I>(faui: (a: A) => Promise<I>) =>
* (ua: Promise<A>): Promise<I> => ua.then(faui);
*
* // Derive a Flatmappable for Promise
* const M = createFlatmappable<KindPromise>({ wrap, flatmap });
*
* const result = await pipe(
* M.wrap((n: number) => (m: number) => n + m),
* M.apply(M.wrap(1)),
* M.apply(M.wrap(1)),
* ); // 2
* ```
*
* @experimental
*
* @since 2.0.0
*/
export function createFlatmappable<U extends Kind>(
{ wrap, flatmap }: Pick<Flatmappable<U>, "wrap" | "flatmap">,
): Flatmappable<U> {
const result: Flatmappable<U> = {
wrap,
apply: ((ua) => flatmap((fai) => result.map(fai)(ua))) as Flatmappable<
U
>["apply"],
map: ((fai) => flatmap((a) => wrap(fai(a)))) as Flatmappable<U>["map"],
flatmap,
};
return result;
}