slang-ts 0.0.1
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/LICENSE +21 -0
- package/README.md +307 -0
- package/dist/index.d.ts +281 -0
- package/dist/index.js +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Hussein Kizz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Slang
|
|
2
|
+
|
|
3
|
+
Functional programming library for TypeScript.
|
|
4
|
+
|
|
5
|
+
My experiment to learn more functional programming and other cool programming stuff from other languages as I try to implement them in TypeScript.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i slang-ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Implemented Utilities
|
|
14
|
+
|
|
15
|
+
- [x] Result (Ok, Err)
|
|
16
|
+
- [x] Maybe (Option)
|
|
17
|
+
- [ ] andThen
|
|
18
|
+
- [x] Atom
|
|
19
|
+
- [x] Expect
|
|
20
|
+
- [x] Unwrap (on Option)
|
|
21
|
+
- [x] Else (on unwrap)
|
|
22
|
+
- [ ] Panic!
|
|
23
|
+
- [x] Zip, Unzip, zipWith
|
|
24
|
+
- [ ] Try
|
|
25
|
+
- [ ] Catch
|
|
26
|
+
- [x] Match
|
|
27
|
+
- [x] MatchAll
|
|
28
|
+
- [ ] Pipe
|
|
29
|
+
- [ ] Map
|
|
30
|
+
- [x] To (converters, e.g. `userAtom.to('option')`)
|
|
31
|
+
- [ ] Promises and async utilities
|
|
32
|
+
- [ ] Curry
|
|
33
|
+
|
|
34
|
+
## Others (Planned)
|
|
35
|
+
|
|
36
|
+
- Pubsub store with state locks
|
|
37
|
+
|
|
38
|
+
## How It Works
|
|
39
|
+
|
|
40
|
+
You can import utilities individually or together:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// Individual imports
|
|
44
|
+
import { option } from "slang-ts";
|
|
45
|
+
import { Ok, Err } from "slang-ts";
|
|
46
|
+
|
|
47
|
+
// Or import multiple at once
|
|
48
|
+
import { option, Ok, Err, atom, match } from "slang-ts";
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### println
|
|
52
|
+
|
|
53
|
+
Well there's nothing special to slang's println utility, its just who wants console.log, its not fun at all, so we instead println, clean and classic, but latter it can be made environment aware so it doesn't print in prod, but for now its just sugar for console.log.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { println } from "slang-ts";
|
|
57
|
+
|
|
58
|
+
const name = "kizz";
|
|
59
|
+
println("name:", name);
|
|
60
|
+
println("multiple", "args", "work", { too: true });
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Option
|
|
64
|
+
|
|
65
|
+
Wraps values that may or may not be present. Returns `Some<T>` for truthy values, `None` for null, undefined, empty strings, NaN, or Infinity. Note that `0` and `false` are truthy as these are usually intentional.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { option } from "slang-ts";
|
|
69
|
+
|
|
70
|
+
const a = option("hi"); // Some("hi")
|
|
71
|
+
const b = option(null); // None
|
|
72
|
+
const c = option(0); // Some(0) - zero is truthy!
|
|
73
|
+
const d = option(""); // None
|
|
74
|
+
const e = option(false); // Some(false) - false is truthy!
|
|
75
|
+
|
|
76
|
+
if (a.isSome) {
|
|
77
|
+
println("Value:", a.value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (b.isNone) {
|
|
81
|
+
println("No value");
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Result
|
|
86
|
+
|
|
87
|
+
Represents operations that can succeed or fail. Returns `Ok<T>` on success or `Err<E>` on failure with typed error payload.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { Ok, Err, type Result } from "slang-ts";
|
|
91
|
+
|
|
92
|
+
// Simple function returning Result
|
|
93
|
+
function divide(a: number, b: number): Result<number, string> {
|
|
94
|
+
if (b === 0) return Err("Cannot divide by zero");
|
|
95
|
+
return Ok(a / b);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = divide(10, 2);
|
|
99
|
+
|
|
100
|
+
if (result.isOk) {
|
|
101
|
+
println("Success:", result.value); // 5
|
|
102
|
+
} else {
|
|
103
|
+
println("Error:", result.error);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Async API example
|
|
107
|
+
interface User {
|
|
108
|
+
id: string;
|
|
109
|
+
name: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function fetchUser(id: string): Promise<Result<User, string>> {
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(`/api/users/${id}`);
|
|
115
|
+
if (!response.ok) return Err("User not found");
|
|
116
|
+
const user = await response.json();
|
|
117
|
+
return Ok(user);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return Err("Network error");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const user = await fetchUser("123");
|
|
124
|
+
if (user.isOk) {
|
|
125
|
+
println("User:", user.value.name);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Atom
|
|
130
|
+
|
|
131
|
+
Creates unique, non-interned symbols with semantic descriptions. Each call produces a distinct identity. So ideally define them in one file and import from it everywhere else, great for env variables stuff.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { atom } from "slang-ts";
|
|
135
|
+
|
|
136
|
+
const userAtom = atom("kizz");
|
|
137
|
+
const user2Atom = atom("kizz");
|
|
138
|
+
|
|
139
|
+
println(userAtom === atom("kizz")); // false - non interned ✅
|
|
140
|
+
println(userAtom.description); // "kizz"
|
|
141
|
+
|
|
142
|
+
if (userAtom === user2Atom) {
|
|
143
|
+
println("all the same");
|
|
144
|
+
} else {
|
|
145
|
+
println("not the same"); // This prints!
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Match
|
|
150
|
+
|
|
151
|
+
Exhaustive pattern matching for `Option` and `Result` types. Forces you to handle all cases.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { match } from "slang-ts";
|
|
155
|
+
|
|
156
|
+
// Matching Results
|
|
157
|
+
const result = divide(10, 0);
|
|
158
|
+
match(result, {
|
|
159
|
+
Ok: (v) => println("Success:", v.value),
|
|
160
|
+
Err: (e) => println("Failed:", e.error),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Matching Options
|
|
164
|
+
const maybePort = option(process.env.PORT);
|
|
165
|
+
match(maybePort, {
|
|
166
|
+
Some: (v) => println("Port:", v.value),
|
|
167
|
+
None: () => println("No port configured"),
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### MatchAll
|
|
172
|
+
|
|
173
|
+
Pattern matching for primitives and atoms with required `_` fallback.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { matchAll } from "slang-ts";
|
|
177
|
+
|
|
178
|
+
// Match atoms
|
|
179
|
+
const ready = atom("ready");
|
|
180
|
+
matchAll(ready, {
|
|
181
|
+
ready: () => println("Ready!"),
|
|
182
|
+
failed: () => println("Failed!"),
|
|
183
|
+
_: () => println("Unknown"),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Match booleans
|
|
187
|
+
const isActive = true;
|
|
188
|
+
matchAll(isActive, {
|
|
189
|
+
true: () => println("Active"),
|
|
190
|
+
false: () => println("Inactive"),
|
|
191
|
+
_: () => println("Unknown"),
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Expect
|
|
196
|
+
|
|
197
|
+
Unwraps values or throws with custom message. Use when failure is unrecoverable.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
const personAge = option(25).expect("a person must have age!");
|
|
201
|
+
println("person age", personAge); // 25
|
|
202
|
+
|
|
203
|
+
// This would throw!
|
|
204
|
+
// const personAge2 = option("").expect("a person must have age!");
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Unwrap/Else
|
|
208
|
+
|
|
209
|
+
Chainable unwrapping with mandatory fallback. Must call `.else()` or throws.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
const port = option(process.env.PORT).unwrap().else(3000);
|
|
213
|
+
println("Using port:", port);
|
|
214
|
+
|
|
215
|
+
// Function fallbacks
|
|
216
|
+
const retries = option(null).unwrap().else(() => 5);
|
|
217
|
+
println("Retries:", retries);
|
|
218
|
+
|
|
219
|
+
// This throws! No .else() chained
|
|
220
|
+
// const nothing = option(null).unwrap();
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### To
|
|
224
|
+
|
|
225
|
+
Converts between Slang types.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
const statusAtom = atom("active").to("option");
|
|
229
|
+
println("Option:", statusAtom); // Some("active")
|
|
230
|
+
|
|
231
|
+
const stateOption = option("ready").to("atom");
|
|
232
|
+
println("Atom:", stateOption.description); // "ready"
|
|
233
|
+
|
|
234
|
+
const errResult = option(null).to("result");
|
|
235
|
+
println("Result:", errResult.type); // "Err"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Zip
|
|
239
|
+
|
|
240
|
+
Combines multiple collections element-wise into tuples.
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { zip } from "slang-ts";
|
|
244
|
+
|
|
245
|
+
// Zip arrays
|
|
246
|
+
const arr1 = [1, 2, 3];
|
|
247
|
+
const arr2 = [4, 5, 6];
|
|
248
|
+
const arr3 = [7, 8, 9];
|
|
249
|
+
println(zip([arr1, arr2, arr3]));
|
|
250
|
+
// [[1,4,7],[2,5,8],[3,6,9]]
|
|
251
|
+
|
|
252
|
+
// Zip with fillValue
|
|
253
|
+
println(zip([arr1, [10, 20]], { fillValue: 0 }));
|
|
254
|
+
// [[1,10],[2,20],[3,0]]
|
|
255
|
+
|
|
256
|
+
// Zip Sets with includeValues=true
|
|
257
|
+
const s1 = new Set([10, 20, 30]);
|
|
258
|
+
const s2 = new Set([100, 200, 300]);
|
|
259
|
+
println(zip([s1, s2], { includeValues: true }));
|
|
260
|
+
// [[10,100],[20,200],[30,300]]
|
|
261
|
+
|
|
262
|
+
// Zip objects with includeValues=true
|
|
263
|
+
const o1 = { a: 1, b: 2, c: 3 };
|
|
264
|
+
const o2 = { x: 100, y: 200, z: 300 };
|
|
265
|
+
println(zip([o1, o2], { includeValues: true }));
|
|
266
|
+
// [[1,100],[2,200],[3,300]]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### ZipWith
|
|
270
|
+
|
|
271
|
+
Combines collections and applies transform function to each tuple.
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { zipWith } from "slang-ts";
|
|
275
|
+
|
|
276
|
+
const arr1 = [1, 2, 3];
|
|
277
|
+
const arr2 = [4, 5, 6];
|
|
278
|
+
const arr3 = [7, 8, 9];
|
|
279
|
+
|
|
280
|
+
println(zipWith([arr1, arr2, arr3], (t) => t.reduce((sum, x) => sum + x, 0)));
|
|
281
|
+
// [12, 15, 18]
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Unzip
|
|
285
|
+
|
|
286
|
+
Reverses zip operation, separating tuples back into arrays.
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { unzip } from "slang-ts";
|
|
290
|
+
|
|
291
|
+
const arr1 = [1, 2, 3];
|
|
292
|
+
const arr2 = [4, 5, 6];
|
|
293
|
+
|
|
294
|
+
const zipped = zip([arr1, arr2]);
|
|
295
|
+
println(unzip(zipped));
|
|
296
|
+
// [[1, 2, 3], [4, 5, 6]]
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
And more are to be implemented in coming versions...
|
|
300
|
+
|
|
301
|
+
## Code Samples
|
|
302
|
+
|
|
303
|
+
See [example.ts](https://github.com/Hussseinkizz/slang/blob/main/example.ts) for usage of currently implemented methods.
|
|
304
|
+
|
|
305
|
+
## Contributing
|
|
306
|
+
|
|
307
|
+
Contributions are welcome, I know there a lot of cool things out there we can bring in.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
export declare const println: (...args: unknown[]) => void;
|
|
2
|
+
/**
|
|
3
|
+
* Converts between Slang types.
|
|
4
|
+
* - `option`: Wraps primitive or symbol description into `Option`
|
|
5
|
+
* - `atom`: Converts `Some<string>` to `Atom` or returns symbol Atom
|
|
6
|
+
* - `result`: Wraps values into `Ok`, `None` into `Err`
|
|
7
|
+
*/
|
|
8
|
+
export declare function _to<T>(value: Option<T>, target: "option"): Option<T>;
|
|
9
|
+
export declare function _to<T extends string>(value: Option<T>, target: "atom"): Atom<T>;
|
|
10
|
+
export declare function _to<T extends string>(value: Atom<T>, target: "option"): Option<string>;
|
|
11
|
+
export declare function _to<T extends string>(value: Atom<T>, target: "atom"): Atom<T>;
|
|
12
|
+
export declare function _to<T, E = string>(value: Option<T>, target: "result"): Result<T, E>;
|
|
13
|
+
export declare function _to<E = string>(value: Atom<any>, target: "result"): Result<string, E>;
|
|
14
|
+
/** Unique symbol to brand atoms */
|
|
15
|
+
declare const __atom__: unique symbol;
|
|
16
|
+
/** Atom type carrying the original name for hover/type info */
|
|
17
|
+
export type Atom<T extends string = string> = symbol & {
|
|
18
|
+
readonly [__atom__]: T;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new, unique atom (non-interned)
|
|
22
|
+
* @param name Name of the atom (used for hover/description)
|
|
23
|
+
* @example
|
|
24
|
+
* const a = atom("loading");
|
|
25
|
+
* typeof a; // Atom<"loading">
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Creates a new, unique atom (non-interned)
|
|
29
|
+
* @param name Name of the atom (used for hover/description)
|
|
30
|
+
* @returns `Atom<T>` with chainable `to()`
|
|
31
|
+
* @example
|
|
32
|
+
* const ready = atom("ready");
|
|
33
|
+
* ready.to("option"); // Some("ready")
|
|
34
|
+
* ready.to("result"); // Ok("ready")
|
|
35
|
+
*/
|
|
36
|
+
/** Methods available on an Atom */
|
|
37
|
+
export interface AtomMethods<T extends string> {
|
|
38
|
+
/** Returns the same atom */
|
|
39
|
+
to(target: "atom"): Atom<T>;
|
|
40
|
+
/** Returns `Option<string>` using the atom description
|
|
41
|
+
* @example atom("ready").to("option") // Some("ready")
|
|
42
|
+
*/
|
|
43
|
+
to(target: "option"): Option<string>;
|
|
44
|
+
/** Returns `Ok<string>` using the atom description
|
|
45
|
+
* @example atom("ready").to("result") // Ok("ready")
|
|
46
|
+
*/
|
|
47
|
+
to(target: "result"): Result<string, string>;
|
|
48
|
+
}
|
|
49
|
+
export declare function atom<const T extends string>(name: T): Atom<T> & AtomMethods<T>;
|
|
50
|
+
declare const __result__: unique symbol;
|
|
51
|
+
export type Ok<T> = {
|
|
52
|
+
type: "Ok";
|
|
53
|
+
value: T;
|
|
54
|
+
readonly isOk: true;
|
|
55
|
+
readonly isErr: false;
|
|
56
|
+
readonly [__result__]: true;
|
|
57
|
+
};
|
|
58
|
+
export type Err<E> = {
|
|
59
|
+
type: "Err";
|
|
60
|
+
error: E;
|
|
61
|
+
readonly isOk: false;
|
|
62
|
+
readonly isErr: true;
|
|
63
|
+
readonly [__result__]: true;
|
|
64
|
+
};
|
|
65
|
+
export type Result<T, E> = (Ok<T> | Err<E>) & ResultMethods<T>;
|
|
66
|
+
/**
|
|
67
|
+
* Creates a new, successful result
|
|
68
|
+
* @param value Value of the result
|
|
69
|
+
* @example
|
|
70
|
+
* const a = Ok("hello");
|
|
71
|
+
* typeof a; // Ok<"hello">
|
|
72
|
+
*/
|
|
73
|
+
/** Methods available on a Result */
|
|
74
|
+
export interface ResultMethods<T> {
|
|
75
|
+
/** Unwraps the value, throwing for Err
|
|
76
|
+
* @example maybeFail().expect("must succeed")
|
|
77
|
+
*/
|
|
78
|
+
expect(msg?: string): T;
|
|
79
|
+
/** Returns an unwrap chain that throws if no else is provided
|
|
80
|
+
* Use `.else(valueOrFn)` to supply a fallback for Err.
|
|
81
|
+
* - If `Ok`, `.else(...)` returns the inner value and ignores fallback.
|
|
82
|
+
* - If `Err`, `.else(...)` returns the fallback; if a function, it receives the error.
|
|
83
|
+
*/
|
|
84
|
+
unwrap(): {
|
|
85
|
+
/** Fallback value or function to recover from Err
|
|
86
|
+
* If a function is provided, it is called with the Err's error.
|
|
87
|
+
* Returns the unwrapped value (Ok) or the provided fallback (Err).
|
|
88
|
+
*/ else(fallback: T | ((error: any) => T)): T;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** Creates a new, successful result */
|
|
92
|
+
export declare function Ok<T>(value: T): Ok<T> & ResultMethods<T>;
|
|
93
|
+
/**
|
|
94
|
+
* Creates a new, failed result
|
|
95
|
+
* @param error Error of the result
|
|
96
|
+
* @example
|
|
97
|
+
* const a = Err("error");
|
|
98
|
+
* typeof a; // Err<"error">
|
|
99
|
+
*/
|
|
100
|
+
/** Creates a new, failed result */
|
|
101
|
+
export declare function Err<E>(error: E): Err<E> & ResultMethods<never>;
|
|
102
|
+
declare const __option__: unique symbol;
|
|
103
|
+
export type Some<T> = {
|
|
104
|
+
type: "Some";
|
|
105
|
+
value: T;
|
|
106
|
+
readonly isSome: true;
|
|
107
|
+
readonly isNone: false;
|
|
108
|
+
readonly [__option__]: true;
|
|
109
|
+
};
|
|
110
|
+
export type None = {
|
|
111
|
+
type: "None";
|
|
112
|
+
readonly isSome: false;
|
|
113
|
+
readonly isNone: true;
|
|
114
|
+
readonly [__option__]: true;
|
|
115
|
+
};
|
|
116
|
+
export type Option<T> = (Some<T> | None) & OptionMethods<T>;
|
|
117
|
+
export type NonTruthy = null | undefined | "";
|
|
118
|
+
/**
|
|
119
|
+
* Creates a new option type from a value
|
|
120
|
+
* @param value - the value to be made an option
|
|
121
|
+
* @tutorial The returned option will be Some if the value is truthy, and None if it is not (null,undefined,Nan,'')
|
|
122
|
+
* @example
|
|
123
|
+
* const b = option("hello");
|
|
124
|
+
* typeof b; // Some<"hello">
|
|
125
|
+
* const c = option(null);
|
|
126
|
+
* typeof c; // None
|
|
127
|
+
* const d = option(undefined);
|
|
128
|
+
* typeof d; // None
|
|
129
|
+
* const e = option(0);
|
|
130
|
+
* typeof e; // Some<number>
|
|
131
|
+
* const f = option("");
|
|
132
|
+
* typeof f; // None
|
|
133
|
+
* const g = option(false);
|
|
134
|
+
* typeof g; // Some<boolean>
|
|
135
|
+
*/
|
|
136
|
+
/** Methods available on an Option */
|
|
137
|
+
export interface OptionMethods<T> {
|
|
138
|
+
/** Returns the same option */
|
|
139
|
+
to(target: "option"): Option<T>;
|
|
140
|
+
/** Converts `Some<string>` to `Atom<string>`; throws for `None` or non-string */
|
|
141
|
+
to(target: "atom"): Atom<T & string>;
|
|
142
|
+
/** Converts to `Result<T, string>`; `None` becomes `Err("Value is None")` */
|
|
143
|
+
to(target: "result"): Result<T | (T extends string ? never : Atom<string>), string>;
|
|
144
|
+
/**
|
|
145
|
+
* Unwraps the option, throwing if `None`.
|
|
146
|
+
* @throws Error with provided message or default.
|
|
147
|
+
* @example
|
|
148
|
+
* option(42).expect(); // 42
|
|
149
|
+
* option("").expect("must be present"); // throws
|
|
150
|
+
*/
|
|
151
|
+
expect(msg?: string): T;
|
|
152
|
+
/** Returns an unwrap chain that MUST be completed with `.else(...)`.
|
|
153
|
+
* If `.else(...)` is not chained, an error is thrown ("Expected else").
|
|
154
|
+
* - If `Some`, `.else(...)` is required but ignored for outcome; returns the inner value.
|
|
155
|
+
* - If `None`, `.else(...)` provides fallback; if a function, it is called with `undefined`.
|
|
156
|
+
* - Fallback result must be truthy; otherwise, throws ("Fallback must be truthy").
|
|
157
|
+
*/
|
|
158
|
+
unwrap(): {
|
|
159
|
+
/** Fallback value or transformer to recover from `None`.
|
|
160
|
+
* - Function form receives `undefined` and must return a truthy value.
|
|
161
|
+
* - Direct value must be truthy.
|
|
162
|
+
* Returns the inner value for `Some`, or the validated fallback for `None`.
|
|
163
|
+
*/ else(fallback: T | ((value: T | undefined) => T)): T;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Creates a new option type from a value.
|
|
168
|
+
* - Truthy values become `Some<T>`; `null|undefined|""` become `None`.
|
|
169
|
+
* - Provides chainable `.to()` and `.expect()` helpers.
|
|
170
|
+
* @example
|
|
171
|
+
* option("hi").expect(); // "hi"
|
|
172
|
+
* option("").expect("cannot be empty"); // throws Error("cannot be empty")
|
|
173
|
+
* option("state").to("atom"); // Atom<"state">
|
|
174
|
+
* option(null).to("result"); // Err("Value is None")
|
|
175
|
+
*/
|
|
176
|
+
export declare function option<T>(value: T | NonTruthy): Option<T> & OptionMethods<T>;
|
|
177
|
+
/**
|
|
178
|
+
* Pattern matching for `Result` and `Option` — exhaustiveness enforced.
|
|
179
|
+
*
|
|
180
|
+
* Returns the value returned by the selected handler. If all handlers return
|
|
181
|
+
* `Result` or `Option`, TypeScript will infer that automatically.
|
|
182
|
+
*/
|
|
183
|
+
export declare function match<T, E, R>(value: Result<T, E>, patterns: {
|
|
184
|
+
Ok: (v: Ok<T>) => R;
|
|
185
|
+
Err: (e: Err<E>) => R;
|
|
186
|
+
}): R;
|
|
187
|
+
export declare function match<T, R>(value: Option<T>, patterns: {
|
|
188
|
+
Some: (v: Some<T>) => R;
|
|
189
|
+
None: (v: None) => R;
|
|
190
|
+
}): R;
|
|
191
|
+
/**
|
|
192
|
+
* Allowed keys in matchAll patterns.
|
|
193
|
+
* - Strings, numbers, and Atom descriptions.
|
|
194
|
+
* - Booleans are represented as "true" | "false" strings
|
|
195
|
+
*/
|
|
196
|
+
type MatchKey = string | number | symbol;
|
|
197
|
+
/**
|
|
198
|
+
* Type-safe pattern object: must always have `_` fallback.
|
|
199
|
+
*/
|
|
200
|
+
type MatchPatterns<V, R> = {
|
|
201
|
+
[K in MatchKey]?: (v: V) => R;
|
|
202
|
+
} & {
|
|
203
|
+
_: () => R;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Matches a value against literal or Atom cases by *semantic name*.
|
|
207
|
+
* - Supports string, number, booleans and Atom (symbol) values.
|
|
208
|
+
* - Unsupported will throw an error. (objects, arrays, functions, etc)
|
|
209
|
+
* - For Atoms, uses their description as a key (e.g. atom("ready") → "ready").
|
|
210
|
+
* - Requires a `_` default handler.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* matchAll(ready, {
|
|
214
|
+
* 1: () => println("One"),
|
|
215
|
+
* 2: () => println("Two"),
|
|
216
|
+
* 0: () => println("Zero"),
|
|
217
|
+
* true: () => println("True"),
|
|
218
|
+
* false: () => println("False"),
|
|
219
|
+
* ready: () => println("Ready!"),
|
|
220
|
+
* failed: () => println("Failed!"),
|
|
221
|
+
* _: () => println("Unknown!"),
|
|
222
|
+
* });
|
|
223
|
+
*/
|
|
224
|
+
export declare function matchAll<T extends MatchKey | boolean, R>(value: T, patterns: MatchPatterns<T, R>): R;
|
|
225
|
+
/**
|
|
226
|
+
* Combines multiple collections element-wise into tuples.
|
|
227
|
+
* - All inputs must be the same type (all arrays, all Sets, or all objects).
|
|
228
|
+
* - By default, only arrays are allowed; set `includeValues: true` to extract values from Sets/Objects.
|
|
229
|
+
* - Stops at shortest collection by default; use `fillValue` to extend to longest.
|
|
230
|
+
* - Transforms columns into rows for parallel iteration.
|
|
231
|
+
* @param inputs Collections of the same type to combine
|
|
232
|
+
* @param options Configuration: `fillValue` extends to longest, `includeValues` extracts values from Sets/Objects (default: false)
|
|
233
|
+
* @returns Array of tuples, one per index position
|
|
234
|
+
* @example
|
|
235
|
+
* zip([[1, 2], ['a', 'b']]); // [[1, 'a'], [2, 'b']]
|
|
236
|
+
* zip([[1, 2], ['a']], { fillValue: 'x' }); // [[1, 'a'], [2, 'x']]
|
|
237
|
+
* const s1 = new Set([1, 2]); const s2 = new Set([3, 4]);
|
|
238
|
+
* zip([s1, s2], { includeValues: true }); // [[1, 3], [2, 4]]
|
|
239
|
+
*/
|
|
240
|
+
export declare function zip<T extends readonly any[]>(inputs: {
|
|
241
|
+
[K in keyof T]: Iterable<T[K]> | {
|
|
242
|
+
[key: string]: T[K];
|
|
243
|
+
};
|
|
244
|
+
}, options?: {
|
|
245
|
+
fillValue?: T[number];
|
|
246
|
+
includeValues?: boolean;
|
|
247
|
+
}): T[number][][];
|
|
248
|
+
/**
|
|
249
|
+
* Combines multiple collections element-wise and transforms each tuple.
|
|
250
|
+
* - All inputs must be the same type (all arrays, all Sets, or all objects).
|
|
251
|
+
* - By default, only arrays are allowed; set `includeValues: true` to extract values from Sets/Objects.
|
|
252
|
+
* - Applies a function to each set of corresponding elements.
|
|
253
|
+
* - Useful for aggregating, computing, or transforming aligned data.
|
|
254
|
+
* @param inputs Collections of the same type to combine
|
|
255
|
+
* @param fn Transform function applied to each tuple
|
|
256
|
+
* @param options Configuration: `fillValue` extends to longest, `includeValues` extracts values from Sets/Objects (default: false)
|
|
257
|
+
* @returns Array of transformed results
|
|
258
|
+
* @example
|
|
259
|
+
* zipWith([[1, 2], [3, 4]], (t) => t[0] + t[1]); // [4, 6]
|
|
260
|
+
* zipWith([[1, 2], [10]], (t) => t.reduce((a, b) => a + b, 0), { fillValue: 0 }); // [11, 2]
|
|
261
|
+
*/
|
|
262
|
+
export declare function zipWith<T extends readonly any[], R>(inputs: {
|
|
263
|
+
[K in keyof T]: Iterable<T[K]> | {
|
|
264
|
+
[key: string]: T[K];
|
|
265
|
+
};
|
|
266
|
+
}, fn: (tuple: T[number][]) => R, options?: {
|
|
267
|
+
fillValue?: T[number];
|
|
268
|
+
includeValues?: boolean;
|
|
269
|
+
}): R[];
|
|
270
|
+
/**
|
|
271
|
+
* Unzips an array of tuples back into separate arrays.
|
|
272
|
+
* - Reverses the zip operation: transforms rows to columns.
|
|
273
|
+
* - Useful for separating previously combined collections.
|
|
274
|
+
* @param zipped Array of tuples (rows) to transpose
|
|
275
|
+
* @returns Array of arrays (columns), one per tuple position
|
|
276
|
+
* @example
|
|
277
|
+
* const zipped = [[1, 'a'], [2, 'b'], [3, 'c']];
|
|
278
|
+
* unzip(zipped); // [[1, 2, 3], ['a', 'b', 'c']]
|
|
279
|
+
*/
|
|
280
|
+
export declare function unzip<T>(zipped: T[][]): T[][];
|
|
281
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var _=(...j)=>{globalThis?.console?.log?.(...j)},Z=(j)=>{let B=globalThis?.queueMicrotask;if(typeof B==="function")B(j);else Promise.resolve().then(j)};function $(j,B){let G=(H)=>H!=null&&typeof H==="object"&&("isSome"in H)&&("isNone"in H);if(j&&(j.type==="Ok"||j.type==="Err"))throw Error("Cannot convert a Result to any other type");switch(B){case"option":{if(G(j))return j;if(typeof j==="symbol")return Y(j.description);return Y(j)}case"atom":{if(G(j)){if(j.isNone)throw Error("Cannot convert None to Atom");if(typeof j.value!=="string")throw Error("Only string values can be converted to Atom");return N(j.value)}if(typeof j==="symbol")return j;throw Error(`Cannot convert type ${typeof j} to Atom`)}case"result":{if(G(j))return j.isSome?X(j.value):R("Value is None");if(typeof j==="symbol")return X(j.description);return X(j)}default:throw Error(`Invalid target: ${B}`)}}function N(j){let B=Symbol(j),G=Object(B),H=(D)=>$(B,D);return G.to=H,G}function X(j){let B=Object.freeze({type:"Ok",value:j,isOk:!0,isErr:!1});return{...B,expect:(H)=>B.value,unwrap:()=>{let H=!1;return Z(()=>{}),{else(D){return H=!0,B.value}}}}}function R(j){let B=Object.freeze({type:"Err",error:j,isOk:!1,isErr:!0}),G=(D,I)=>{if(typeof D==="string")return D;if(D&&typeof D==="object"&&"message"in D)return String(D.message);return I??String(D)};return{...B,expect:(D)=>{throw Error(D??G(B.error,"Expected Ok, got Err"))},unwrap:()=>{let D=!1;return Z(()=>{if(!D){let I=G(B.error,"Expected Ok, got Err");throw Error(I)}}),{else(I){if(D=!0,typeof I==="function")return I(B.error);return I}}}}}var C=(j)=>{return j===null||j===void 0||j===""||Number.isNaN(j)||j===1/0||j===-1/0};function q(j){if(C(j))throw Error("Cannot wrap null, undefined, NaN, or empty string in Some");return Object.freeze({type:"Some",value:j,isSome:!0,isNone:!1})}var F=Object.freeze({type:"None",isSome:!1,isNone:!0});function Y(j){let B=C(j)?F:q(j);return{...B,to:(P)=>$(B,P),expect:(P)=>{if(B.isSome)return B.value;throw Error(P??"Expected Some, got None")},unwrap:()=>{let P=!1,Q=B;return Z(()=>{if(!P)throw Error("Expected else")}),{else(T){if(P=!0,Q.isSome)return Q.value;let J=typeof T==="function"?T(void 0):T;if(Y(J).isNone)throw Error("Fallback must be truthy");return J}}}}}function A(j,B){let G=B[j.type];if(!G)throw Error(`Non-exhaustive match — missing handler for '${j.type}'`);return G(j)}var L=(j)=>{return typeof j==="string"||typeof j==="number"||typeof j==="boolean"||typeof j==="symbol"};function E(j,B){let G=(Q)=>typeof Q?.valueOf==="function"?Q.valueOf():Q,H=(Q)=>typeof Q==="symbol"?Q.description:void 0,D=G(j);if(!L(D))throw Error(`Unsupported match all value type: ${typeof D}`);let I=H(D)??D,P=typeof I==="boolean"||typeof I==="number"?String(I):I;if(P!=null&&P in B)return B[P](j);return B._()}function S(j,B=!1){if(!B&&!Array.isArray(j))throw Error("Only arrays allowed when includeValues=false");if(Array.isArray(j))return j;if(j instanceof Set)return Array.from(j);return Object.values(j)}function U(j,B){let{fillValue:G,includeValues:H=!1}=B||{};if(j.length===0)return[];let D=j.map((J)=>S(J,H)),I=Math.max(...D.map((J)=>J.length)),P=Math.min(...D.map((J)=>J.length)),Q=G===void 0?P:I,T=[];for(let J=0;J<Q;J++)T.push(D.map((W)=>J<W.length?W[J]:G));return T}function K(j,B,G){return U(j,G).map(B)}function M(j){if(j.length===0)return[];let B=j[0]?.length??0,G=Array.from({length:B},()=>[]);for(let H of j)H.forEach((D,I)=>G[I]?.push(D));return G}export{K as zipWith,U as zip,M as unzip,_ as println,Y as option,E as matchAll,A as match,N as atom,$ as _to,X as Ok,R as Err};
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "slang-ts",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Functional programming library for TypeScript",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"test:ui": "vitest --ui",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"build": "bun run test && bun run typecheck && bun run bun-build.ts && tsc -p tsconfig.build.json"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/Hussseinkizz/slang#readme",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/Hussseinkizz/slang.git"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/Hussseinkizz/slang/issues"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"functional",
|
|
32
|
+
"typescript",
|
|
33
|
+
"option",
|
|
34
|
+
"result",
|
|
35
|
+
"pattern-matching",
|
|
36
|
+
"monad",
|
|
37
|
+
"ts-pattern",
|
|
38
|
+
"defensive programming",
|
|
39
|
+
"fp-ts",
|
|
40
|
+
"ramda",
|
|
41
|
+
"rameda",
|
|
42
|
+
"neverthrow",
|
|
43
|
+
"lodash"
|
|
44
|
+
],
|
|
45
|
+
"author": "Hussein Kizz",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"sideEffects": false,
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/bun": "latest",
|
|
50
|
+
"@vitest/ui": "^4.0.15",
|
|
51
|
+
"vitest": "^4.0.15"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"typescript": "^5"
|
|
55
|
+
}
|
|
56
|
+
}
|