├── .vscode └── settings.json ├── Array.ts ├── Array_test.ts ├── Function.ts ├── LICENSE ├── Number.ts ├── Number_test.ts ├── Option.ts ├── Option_test.ts ├── README.md ├── String.ts ├── String_test.ts ├── Typeguards.ts ├── deno.jsonc ├── deno.lock ├── docs ├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.js ├── package.json ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── _meta.json │ ├── api │ │ └── hello.ts │ ├── array.mdx │ ├── function.mdx │ ├── index.mdx │ ├── option.mdx │ └── string.mdx ├── pnpm-lock.yaml ├── public │ ├── favicon.ico │ ├── next.svg │ ├── thirteen.svg │ └── vercel.svg ├── theme.config.jsx └── tsconfig.json └── functions ├── compose.ts ├── compose_test.ts ├── pipe.ts └── pipe_test.ts /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true 3 | } 4 | -------------------------------------------------------------------------------- /Array.ts: -------------------------------------------------------------------------------- 1 | import { type Option, some, none, match } from "./Option.ts"; 2 | 3 | /** 4 | * Returns true if all elements in the array match the predicate, false otherwise. 5 | * 6 | * @param array - The array to operate on 7 | * @param predicate - The predicate function to run on each element 8 | * 9 | * @example 10 | * ``` 11 | * const x = pipe( 12 | * [1, 2, 3, 4, 5], 13 | * A.all((n) => n > 0) 14 | * ); 15 | * 16 | * const y = pipe( 17 | * ["hi", "hello", "howdy", "bye", "hey there"], 18 | * A.all((str) => str.startsWith("h")) 19 | * ); 20 | * 21 | * 22 | * assertEquals(x, true); 23 | * assertEquals(y, false); 24 | * ``` 25 | */ 26 | export function all(array: T[], predicate: (value: T) => boolean): boolean; 27 | export function all( 28 | predicate: (value: T) => boolean 29 | ): (array: T[]) => boolean; 30 | export function all( 31 | arrayOrPredicate: T[] | ((value: T) => boolean), 32 | predicate?: (value: T) => boolean 33 | ): boolean | ((array: T[]) => boolean) { 34 | return arguments.length === 1 35 | ? (array: T[]) => all(array, arrayOrPredicate as (value: T) => boolean) 36 | : (arrayOrPredicate as T[]).every(predicate as (value: T) => boolean); 37 | } 38 | 39 | /** 40 | * Returns true if any element in the array matches the predicate, false otherwise. 41 | * 42 | * @param array - The array to operate on 43 | * @param predicate - The predicate function to run on each element 44 | * 45 | * @example 46 | * ``` 47 | * const x = pipe( 48 | * ["hi", "hello", "howdy", "bye", "hey there"], 49 | * A.any((str) => str.startsWith("h")) 50 | * ); 51 | * 52 | * const y = pipe( 53 | * [1, 2, 3, 4, 5], 54 | * A.any((n) => n < 0) 55 | * ); 56 | * 57 | * 58 | * assertEquals(x, true); 59 | * assertEquals(y, false); 60 | * ``` 61 | */ 62 | export function any(array: T[], predicate: (value: T) => boolean): boolean; 63 | export function any( 64 | predicate: (value: T) => boolean 65 | ): (array: T[]) => boolean; 66 | export function any( 67 | arrayOrPredicate: T[] | ((value: T) => boolean), 68 | predicate?: (value: T) => boolean 69 | ): boolean | ((array: T[]) => boolean) { 70 | return arguments.length === 1 71 | ? (array: T[]) => any(array, arrayOrPredicate as (value: T) => boolean) 72 | : (arrayOrPredicate as T[]).some(predicate as (value: T) => boolean); 73 | } 74 | 75 | /** 76 | * Returns a new array with the element appended to the end. 77 | * 78 | * @param array - The array to operate on 79 | * @param element - The element to append to the array 80 | * 81 | * @example 82 | * ``` 83 | * const x = pipe([1, 2, 3, 4, 5], A.append(6)); 84 | * const y = pipe(["hi", "hello", "howdy"], A.append("bye")); 85 | * 86 | * assertEquals(x, [1, 2, 3, 4, 5, 6]); 87 | * assertEquals(y, ["hi", "hello", "howdy", "bye"]); 88 | * ``` 89 | */ 90 | export function append(array: T[], element: T): T[]; 91 | export function append(element: T): (array: T[]) => T[]; 92 | export function append( 93 | arrayOrElement: T[] | T, 94 | element?: T 95 | ): T[] | ((array: T[]) => T[]) { 96 | return arguments.length === 1 97 | ? (array: T[]) => append(array, arrayOrElement as T) 98 | : [...(arrayOrElement as T[]), element as T]; 99 | } 100 | 101 | /** 102 | * Returns an Option of Some type if an element is present at the given index, or None if the there is no element at the given index. 103 | * 104 | * @param array - The array to operate on 105 | * @param index - The index of the element to return 106 | * 107 | * @example 108 | * ``` 109 | * const x = pipe( 110 | * [1, 2, 3, 4, 5], 111 | * A.at(2), 112 | * O.unwrapOr(0) 113 | * ); 114 | * 115 | * const y = pipe( 116 | * [1, 2, 3, 4, 5], 117 | * A.at(10), 118 | * O.match( 119 | * (n) => `${n} is at index 10`, 120 | * () => "No element at index 10" 121 | * ) 122 | * ); 123 | * 124 | * assertEquals(x, 3); 125 | * assertEquals(y, "No element at index 10"); 126 | * ``` 127 | */ 128 | export function at(array: T[], index: number): Option; 129 | export function at(index: number): (array: T[]) => Option; 130 | export function at( 131 | arrayOrIndex: T[] | number, 132 | index?: number 133 | ): Option | ((array: T[]) => Option) { 134 | if (arguments.length === 1) { 135 | return (array: T[]) => at(array, arrayOrIndex as number); 136 | } 137 | 138 | return (arrayOrIndex as T[])[index as number] === undefined 139 | ? none 140 | : some((arrayOrIndex as T[])[index as number]); 141 | } 142 | 143 | /** 144 | * Retuns a new array with the other array concatenated to the end of the first array. 145 | * 146 | * @param array - The array to operate on 147 | * @param other - The array to concat to the end of the first array 148 | * 149 | * @example 150 | * ``` 151 | * const x = pipe( 152 | * [1, 2, 3], 153 | * A.concat([4, 5]) 154 | * ); 155 | * 156 | * assertEquals(x, [1, 2, 3, 4, 5]); 157 | * ``` 158 | */ 159 | export function concat(array: T[], other: T[]): T[]; 160 | export function concat(other: T[]): (array: T[]) => T[]; 161 | export function concat( 162 | arrayOrOther: T[], 163 | other?: T[] 164 | ): T[] | ((array: T[]) => T[]) { 165 | return arguments.length === 1 166 | ? (array: T[]) => concat(array, arrayOrOther) 167 | : [...arrayOrOther, ...(other as T[])]; 168 | } 169 | 170 | /** 171 | * Returns a new clone of the array. 172 | * 173 | * @param array - The array to operate on 174 | * 175 | * @example 176 | * ``` 177 | * const x = A.clone([1, 2, 3, 4, 5]); 178 | * 179 | * assertEquals(x, [1, 2, 3, 4, 5]); 180 | * ``` 181 | */ 182 | export function clone(array: T[]): T[] { 183 | return [...array]; 184 | } 185 | 186 | /** 187 | * Returns a new array containing the elements of the array that are not present in the other array. 188 | * 189 | * @param array - The array to operate on 190 | * @param other - The array to compare to 191 | * 192 | * @example 193 | * ``` 194 | * const x = pipe( 195 | * [1, 2, 3, 4, 5], 196 | * A.diff([1, 2, 3]) 197 | * ); 198 | * 199 | * const y = pipe( 200 | * [1, 2, 3, 4, 5], 201 | * A.diff([1, 2, 3, 4, 5]) 202 | * ); 203 | * 204 | * assertEquals(x, [4, 5]); 205 | * assertEquals(y, []); 206 | * ``` 207 | */ 208 | export function diff(array: T[], other: T[]): T[]; 209 | export function diff(other: T[]): (array: T[]) => T[]; 210 | export function diff( 211 | arrayOrOther: T[], 212 | other?: T[] 213 | ): T[] | ((array: T[]) => T[]) { 214 | return arguments.length === 1 215 | ? (array: T[]) => diff(array, arrayOrOther) 216 | : arrayOrOther.filter((e) => !(other as T[]).includes(e)); 217 | } 218 | 219 | /** 220 | * Retuns a new array which is an Option of Some type with n elements dropped from the beginning of the array. None is returned if n is greater than the length of the array or negative. 221 | * 222 | * @param array - The array to operate on 223 | * @param n - The number of elements to drop from the beginning of the array 224 | * 225 | * @example 226 | * ``` 227 | * const x = pipe([1, 2, 3, 4, 5], A.drop(2), O.unwrapOr([] as number[])); 228 | * const y = pipe([1, 2, 3, 4, 5], A.drop(10), O.unwrapOr([] as number[])); 229 | * const z = pipe([1, 2, 3, 4, 5], A.drop(-1), O.unwrapOr([] as number[])); 230 | * 231 | * assertEquals(x, [3, 4, 5]); 232 | * assertEquals(y, []); 233 | * assertEquals(z, []); 234 | * ``` 235 | */ 236 | export function drop(array: T[], n: number): Option; 237 | export function drop(n: number): (array: T[]) => Option; 238 | export function drop( 239 | arrayOrN: T[] | number, 240 | n?: number 241 | ): Option | ((array: T[]) => Option) { 242 | if (arguments.length === 1) { 243 | return (array: T[]) => drop(array, arrayOrN as number); 244 | } 245 | 246 | return n! < 0 || n! > (arrayOrN as T[]).length 247 | ? none 248 | : some((arrayOrN as T[]).slice(n!)); 249 | } 250 | 251 | /** 252 | * Returns a new array with n elements dropped from the beginning of the array until the first element that does not satisfy the predicate. 253 | * 254 | * @param array - The array to operate on 255 | * @param predicate - The predicate to test each element against 256 | * 257 | * @example 258 | * ``` 259 | * const x = pipe( 260 | * [1, 2, 3, 4, 5], 261 | * A.dropWhile((n) => n < 3) 262 | * ); 263 | * 264 | * assertEquals(x, [3, 4, 5]); 265 | * ``` 266 | */ 267 | export function dropWhile( 268 | array: T[], 269 | predicate: (value: T, index: number) => boolean 270 | ): T[]; 271 | export function dropWhile( 272 | predicate: (value: T, index: number) => boolean 273 | ): (array: T[]) => T[]; 274 | export function dropWhile( 275 | arrayOrPredicate: T[] | ((value: T, index: number) => boolean), 276 | predicate?: (value: T, index: number) => boolean 277 | ): T[] | ((array: T[]) => T[]) { 278 | if (arguments.length === 1) { 279 | return (array: T[]) => 280 | dropWhile( 281 | array, 282 | arrayOrPredicate as (value: T, index: number) => boolean 283 | ); 284 | } 285 | 286 | const index = (arrayOrPredicate as T[]).findIndex( 287 | (value, index) => !predicate!(value, index) 288 | ); 289 | 290 | return index === -1 ? [] : (arrayOrPredicate as T[]).slice(index); 291 | } 292 | 293 | /** 294 | * Returns a new array containing the elements of the array that satisfy the predicate. 295 | * 296 | * @param array - The array to operate on 297 | * @param predicate - The predicate to filter the array with 298 | * 299 | * @example 300 | * ``` 301 | * const x = pipe( 302 | * [1, 2, 3, 4, 5], 303 | * A.filter((n) => n % 2 === 0) 304 | * ); 305 | * 306 | * const y = pipe( 307 | * [1, 2, 3, 4, 5], 308 | * A.filter((_, i) => i % 2 === 0) 309 | * ); 310 | * 311 | * assertEquals(x, [2, 4]); 312 | * assertEquals(y, [1, 3, 5]); 313 | * ``` 314 | */ 315 | export function filter( 316 | array: T[], 317 | predicate: (value: T, index: number) => boolean 318 | ): T[]; 319 | export function filter( 320 | predicate: (value: T, index: number) => boolean 321 | ): (array: T[]) => T[]; 322 | export function filter( 323 | arrayOrPredicate: T[] | ((value: T, index: number) => boolean), 324 | predicate?: (value: T, index: number) => boolean 325 | ): T[] | ((array: T[]) => T[]) { 326 | return arguments.length === 1 327 | ? (array: T[]) => 328 | filter(array, arrayOrPredicate as (value: T, index: number) => boolean) 329 | : (arrayOrPredicate as T[]).filter((value, index) => 330 | predicate!(value, index) 331 | ); 332 | } 333 | 334 | /** 335 | * filterMap 336 | * Returns a new array containing the elements of the array that satisfy the predicate. The predicate function returns an Option, if the Option is Some, the value is included in the new array, if the Option is None, the value is excluded in the new array. 337 | * 338 | * @param array - The array to operate on 339 | * @param fn - The predicate to filter the array with 340 | * 341 | * @example 342 | * ``` 343 | * const x = pipe( 344 | * [1, 2, 3, 4, 5], 345 | * A.filterMap((n) => n % 2 === 0 ? O.some(n) : O.none) 346 | * ); 347 | * 348 | * const y = pipe( 349 | * ["a", "b", "c", "d", "e"], 350 | * A.filterMap((s, i) => i % 2 === 0 ? O.some(s) : O.none) 351 | * ); 352 | * 353 | * assertEquals(x, [2, 4]); 354 | * assertEquals(y, ["a", "c", "e"]); 355 | * ``` 356 | */ 357 | export function filterMap( 358 | array: T[], 359 | fn: (value: T, index: number) => Option 360 | ): U[]; 361 | export function filterMap( 362 | fn: (value: T, index: number) => Option 363 | ): (array: T[]) => U[]; 364 | export function filterMap( 365 | arrayOrFn: T[] | ((value: T, index: number) => Option), 366 | fn?: (value: T, index: number) => Option 367 | ): U[] | ((array: T[]) => U[]) { 368 | if (arguments.length === 1) { 369 | return (array: T[]) => 370 | filterMap(array, arrayOrFn as (value: T, index: number) => Option); 371 | } 372 | 373 | return (arrayOrFn as T[]).reduce((acc, value, index) => { 374 | const result = fn!(value, index); 375 | 376 | return match( 377 | result, 378 | (value) => [...acc, value], 379 | () => acc 380 | ); 381 | }, []); 382 | } 383 | 384 | /** 385 | * Returns an Option of Some type for the first element of the array that satisfies the predicate. None is returned if no element satisfies the predicate. 386 | * 387 | * @param array - The array to operate on 388 | * @param predicate - The predicate to find the element 389 | * 390 | * @example 391 | * ``` 392 | * const x = pipe( 393 | * [1, 2, 3, 4, 5], 394 | * A.find((x) => x > 3), 395 | * O.unwrapOr(0) 396 | * ); 397 | * 398 | * const y = pipe( 399 | * [1, 2, 3, 4, 5], 400 | * A.find((x) => x < 0), 401 | * O.unwrapOr(0) 402 | * ); 403 | * 404 | * assertEquals(x, 4); 405 | * assertEquals(y, 0); 406 | * ``` 407 | */ 408 | export function find( 409 | array: T[], 410 | predicate: (value: T) => boolean 411 | ): Option; 412 | export function find( 413 | predicate: (value: T) => boolean 414 | ): (array: T[]) => Option; 415 | export function find( 416 | arrayOrPredicate: T[] | ((value: T) => boolean), 417 | predicate?: (value: T) => boolean 418 | ): Option | ((array: T[]) => Option) { 419 | if (arguments.length === 1) { 420 | return (array: T[]) => 421 | find(array, arrayOrPredicate as (value: T) => boolean); 422 | } 423 | 424 | const result = (arrayOrPredicate as T[]).find(predicate!); 425 | 426 | return result === undefined ? none : some(result); 427 | } 428 | 429 | /** 430 | * Retuns an Option of Some type for the index of the first element of the array that satisfies the predicate. None is returned if no element satisfies the predicate. 431 | * 432 | * @param array - The array to operate on 433 | * @param predicate - The predicate to find the index of the element 434 | * 435 | * @example 436 | * ``` 437 | * const x = pipe( 438 | * [1, 2, 3, 4, 5], 439 | * A.findIndex((x) => x > 3), 440 | * O.unwrapOr(0) 441 | * ); 442 | * 443 | * const y = pipe( 444 | * [1, 2, 3, 4, 5], 445 | * A.findIndex((x) => x < 0), 446 | * O.unwrapOr(0) 447 | * ); 448 | * 449 | * assertEquals(x, 3); 450 | * assertEquals(y, 0); 451 | * ``` 452 | */ 453 | export function findIndex( 454 | array: T[], 455 | predicate: (value: T) => boolean 456 | ): Option; 457 | export function findIndex( 458 | predicate: (value: T) => boolean 459 | ): (array: T[]) => Option; 460 | export function findIndex( 461 | arrayOrPredicate: T[] | ((value: T) => boolean), 462 | predicate?: (value: T) => boolean 463 | ): Option | ((array: T[]) => Option) { 464 | if (arguments.length === 1) { 465 | return (array: T[]) => 466 | findIndex(array, arrayOrPredicate as (value: T) => boolean); 467 | } 468 | 469 | const result = (arrayOrPredicate as T[]).findIndex(predicate!); 470 | 471 | return result === -1 ? none : some(result); 472 | } 473 | 474 | /** 475 | * Returns a new array with all the sub arrays concatenated into it at a single level of depth. 476 | * 477 | * @param array - The array to operate on 478 | * 479 | * @example 480 | * ``` 481 | * const x = pipe( 482 | * [1, 2, [3, 4, [5, 6]], 7, 8], 483 | * A.flatten 484 | * ) 485 | * 486 | * assertEquals(x, [1, 2, 3, 4, 5, 6, 7, 8]); 487 | * ``` 488 | */ 489 | export function flatten(array: T[]): (T extends (infer U)[] ? U : T)[] { 490 | return array.reduce( 491 | (acc, cur) => [...acc, ...(Array.isArray(cur) ? flatten(cur) : [cur])], 492 | [] as (T extends (infer B)[] ? B : T)[] 493 | ); 494 | } 495 | 496 | /** 497 | * Returns an Option of Some type of the first element of the array and None if the array is empty. 498 | * 499 | * @param array - The array to operate on 500 | * 501 | * @example 502 | * ``` 503 | * const x = pipe([1, 2, 3, 4, 5], A.head, O.unwrapOr(0)); 504 | * const y = pipe([], A.head, O.unwrapOr(0)); 505 | * 506 | * assertEquals(x, 1); 507 | * assertEquals(y, 0); 508 | * ``` 509 | */ 510 | export function head(array: T[]): Option { 511 | return array.length === 0 ? none : some(array[0]); 512 | } 513 | 514 | /** 515 | * Retuns a new array of the intersection of the two arrays. 516 | * 517 | * @param array - The array to operate on 518 | * @param other - The other array that will be used to find the intersection 519 | * 520 | * @example 521 | * ``` 522 | * const x = pipe( 523 | * [1, 2, 3, 4, 5], 524 | * A.intersection([3, 4, 5, 6, 7]) 525 | * ); 526 | * 527 | * const y = pipe( 528 | * [1, 2, 1, 1, 3], 529 | * A.intersection([1]) 530 | * ); 531 | * 532 | * assertEquals(x, [3, 4, 5]); 533 | * assertEquals(y, [1]); 534 | * ``` 535 | */ 536 | export function intersection(array: T[], other: T[]): T[]; 537 | export function intersection(other: T[]): (array: T[]) => T[]; 538 | export function intersection( 539 | arrayOrOther: T[] | T[], 540 | other?: T[] 541 | ): T[] | ((array: T[]) => T[]) { 542 | return arguments.length === 1 543 | ? (array: T[]) => intersection(array, arrayOrOther as T[]) 544 | : [...new Set(arrayOrOther)].filter((value) => new Set(other!).has(value)); 545 | } 546 | 547 | /** 548 | * Returns a new array with the separator interspersed between each element of the array. 549 | * 550 | * @param array - The array to operate on 551 | * @param separator - The separator to intersperse the array with 552 | * 553 | * @example 554 | * ``` 555 | * const x = pipe( 556 | * [1, 2, 3], 557 | * A.intersperse(0) 558 | * ); 559 | * 560 | * assertEquals(x, [1, 0, 2, 0, 3]); 561 | * ``` 562 | */ 563 | export function intersperse(array: T[], separator: T): T[]; 564 | export function intersperse(separator: T): (array: T[]) => T[]; 565 | export function intersperse( 566 | arrayOrSeparator: T[] | T, 567 | separator?: T 568 | ): T[] | ((array: T[]) => T[]) { 569 | if (arguments.length === 1) { 570 | return (array: T[]) => intersperse(array, arrayOrSeparator as T); 571 | } 572 | 573 | return (arrayOrSeparator as T[]).reduce( 574 | (acc, cur, index) => 575 | index === 0 ? [...acc, cur] : [...acc, separator!, cur], 576 | [] as T[] 577 | ); 578 | } 579 | 580 | /** 581 | * Returns true if the array is empty, false otherwise. 582 | * 583 | * @param array - The array to operate on 584 | * 585 | * @example 586 | * ``` 587 | * const x = pipe([1, 2, 3], A.isEmpty); 588 | * const y = pipe([], A.isEmpty); 589 | * 590 | * assertEquals(x, false); 591 | * assertEquals(y, true); 592 | * ``` 593 | */ 594 | export function isEmpty(array: T[]): boolean { 595 | return array.length === 0; 596 | } 597 | 598 | /** 599 | * Returns false if the array is empty, true otherwise. 600 | * 601 | * @param array - The array to operate on 602 | * 603 | * @example 604 | * ``` 605 | * const x = pipe([1, 2, 3], A.isNonEmpty); 606 | * const y = pipe([], A.isNonEmpty); 607 | * 608 | * assertEquals(x, true); 609 | * assertEquals(y, false); 610 | * ``` 611 | */ 612 | export function isNonEmpty(array: T[]): boolean { 613 | return array.length !== 0; 614 | } 615 | 616 | /** 617 | * Returns a string of all the elements of the array separated by the separator. 618 | * 619 | * @param array - The array to operate on 620 | * @param separator - The separator to join the array with 621 | * 622 | * @example 623 | * ``` 624 | * const x = pipe( 625 | * [1, 2, 3], 626 | * A.join("-") 627 | * ); 628 | * 629 | * assertEquals(x, "1-2-3"); 630 | * ``` 631 | */ 632 | export function join(array: T[], separator: string): string; 633 | export function join(separator: string): (array: T[]) => string; 634 | export function join( 635 | arrayOrSeparator: T[] | string, 636 | separator?: string 637 | ): string | ((array: T[]) => string) { 638 | return arguments.length === 1 639 | ? (array: T[]) => join(array, arrayOrSeparator as string) 640 | : (arrayOrSeparator as T[]).join(separator!); 641 | } 642 | 643 | /** 644 | * Retuns an Option of Some type of the last element of the array and None if the array is empty. 645 | * 646 | * @param array - The array to operate on 647 | * 648 | * @example 649 | * ``` 650 | * const x = pipe([1, 2, 3, 4, 5], A.last, O.unwrapOr(0)); 651 | * const y = pipe([], A.last, O.unwrapOr(0)); 652 | * 653 | * assertEquals(x, 5); 654 | * assertEquals(y, 0); 655 | * ``` 656 | */ 657 | export function last(array: T[]): Option { 658 | return array.length === 0 ? none : some(array[array.length - 1]); 659 | } 660 | 661 | /** 662 | * Returns the length of the array. 663 | * 664 | * @param array - The array to operate on 665 | * 666 | * @example 667 | * ``` 668 | * const x = pipe([1, 2, 3, 4, 5], A.length); 669 | * 670 | * assertEquals(x, 5); 671 | * ``` 672 | */ 673 | export function length(array: T[]): number { 674 | return array.length; 675 | } 676 | 677 | /** 678 | * Returns a new array with the result of calling the function on each element of the array. 679 | * 680 | * @param array - The array to operate on 681 | * @param fn - The function to map the array with 682 | * 683 | * @example 684 | * ``` 685 | * const x = pipe( 686 | * [1, 2, 3, 4, 5], 687 | * A.map((n) => n * 2) 688 | * ); 689 | * 690 | * const y = pipe( 691 | * [1, 2, 3, 4, 5], 692 | * A.map((n, i) => n + i) 693 | * ); 694 | * 695 | * assertEquals(x, [2, 4, 6, 8, 10]); 696 | * assertEquals(y, [1, 3, 5, 7, 9]); 697 | * ``` 698 | */ 699 | export function map(array: T[], fn: (value: T, index: number) => U): U[]; 700 | export function map( 701 | fn: (value: T, index: number) => U 702 | ): (array: T[]) => U[]; 703 | export function map( 704 | arrayOrFn: T[] | ((value: T, index: number) => U), 705 | fn?: (value: T, index: number) => U 706 | ): U[] | ((array: T[]) => U[]) { 707 | return arguments.length === 1 708 | ? (array: T[]) => map(array, arrayOrFn as (value: T, index: number) => U) 709 | : (arrayOrFn as T[]).map((value, index) => fn!(value, index)); 710 | } 711 | 712 | /** 713 | * Returns an Option of Some type of the maximum value of an array of numbers and None if the array is empty. 714 | * 715 | * @param array - The array to operate on 716 | * 717 | * @example 718 | * ``` 719 | * const x = pipe([1, 2, 3, 4, 5], A.max, O.unwrapOr(0)); 720 | * const y = pipe([], A.max, O.unwrapOr(0)); 721 | * 722 | * assertEquals(x, 5); 723 | * assertEquals(y, 0); 724 | * ``` 725 | */ 726 | export function max(array: number[]): Option { 727 | return array.length === 0 ? none : some(Math.max(...array)); 728 | } 729 | 730 | /** 731 | * Returns an Option of Some type of the maximum value of an array of numbers and None if the array is empty. 732 | * @param array - The array to operate on 733 | * 734 | * @example 735 | * ``` 736 | * const x = pipe([1, 2, 3, 4, 5], A.min, O.unwrapOr(0)); 737 | * const y = pipe([], A.min, O.unwrapOr(0)); 738 | * 739 | * assertEquals(x, 1); 740 | * assertEquals(y, 0); 741 | * ``` 742 | */ 743 | export function min(array: number[]): Option { 744 | return array.length === 0 ? none : some(Math.min(...array)); 745 | } 746 | 747 | /** 748 | * Returns a tuple of two arrays, the first containing the elements that satisfy the predicate and the second containing the elements that do not satisfy the predicate. 749 | * 750 | * @param array - The array to operate on 751 | * @param predicate - The predicate to partition the array with 752 | * 753 | * @example 754 | * ``` 755 | * const x = pipe( 756 | * [1, "a", 2, "b", 3, "c"], 757 | * A.partition(G.isNumber) 758 | * ); 759 | * 760 | * assertEquals(x, [[1, 2, 3], ["a", "b", "c"]]); 761 | * ``` 762 | */ 763 | export function partition( 764 | array: T[], 765 | predicate: (value: T, index: number) => value is U 766 | ): readonly [U[], Exclude[]]; 767 | export function partition( 768 | predicate: (value: T, index: number) => value is U 769 | ): (array: T[]) => readonly [U[], Exclude[]]; 770 | export function partition( 771 | array: T[], 772 | predicate: (value: T, index: number) => boolean 773 | ): readonly [T[], T[]]; 774 | export function partition( 775 | predicate: (value: T, index: number) => boolean 776 | ): (array: T[]) => readonly [T[], T[]]; 777 | export function partition( 778 | arrayOrPredicateFn: T[] | ((value: T, index: number) => boolean), 779 | predicate?: (value: T, index: number) => boolean 780 | ): readonly [T[], T[]] | ((array: T[]) => readonly [T[], T[]]) { 781 | return arguments.length === 1 782 | ? (array: T[]) => 783 | partition( 784 | array, 785 | arrayOrPredicateFn as (value: T, index: number) => boolean 786 | ) 787 | : (arrayOrPredicateFn as T[]).reduce( 788 | (acc, cur, i) => { 789 | acc[predicate!(cur, i) ? 0 : 1].push(cur); 790 | return acc; 791 | }, 792 | [[], []] as readonly [T[], T[]] 793 | ); 794 | } 795 | 796 | /** 797 | * Returns a new array with the value prepended to the beginning of the array. 798 | * 799 | * @param value - The value to prepend to the array 800 | * @param array - The array to operate on 801 | * 802 | * @example 803 | * ``` 804 | * const x = pipe( 805 | * [1, 2, 3, 4, 5], 806 | * A.prepend(0) 807 | * ); 808 | * 809 | * assertEquals(x, [0, 1, 2, 3, 4, 5]); 810 | * ``` 811 | */ 812 | export function prepend(value: T, array: T[]): T[]; 813 | export function prepend(value: T): (array: T[]) => T[]; 814 | export function prepend(value: T, array?: T[]): T[] | ((array: T[]) => T[]) { 815 | return arguments.length === 1 816 | ? (array: T[]) => prepend(value, array) 817 | : [value, ...array!]; 818 | } 819 | 820 | /** 821 | * Returns the product of all the elements in a array of numbers. 822 | * 823 | * @param array - The array to operate on 824 | * 825 | * @example 826 | * ``` 827 | * const x = pipe([1, 2, 3, 4, 5], A.product); 828 | * 829 | * assertEquals(x, 120); 830 | * ``` 831 | */ 832 | export function product(array: number[]): number { 833 | return array.reduce((acc, cur) => acc * cur, 1); 834 | } 835 | 836 | /** 837 | * Reduces an array to a single value by applying a function to each element and accumulating the result. 838 | * 839 | * @param array - The array to operate on 840 | * @param fn - The function to reduce the array with 841 | * @param initial - The initial value to reduce the array with 842 | * 843 | * @example 844 | * ``` 845 | * const x = pipe( 846 | * [1, 2, 3, 4, 5], 847 | * A.reduce((acc, cur) => acc + cur, 0) 848 | * ); 849 | * 850 | * assertEquals(x, 15); 851 | * ``` 852 | */ 853 | export function reduce( 854 | array: T[], 855 | fn: (acc: U, value: T, index: number) => U, 856 | initial: U 857 | ): U; 858 | export function reduce( 859 | fn: (acc: U, value: T, index: number) => U, 860 | initial: U 861 | ): (array: T[]) => U; 862 | export function reduce( 863 | arrayOrFn: T[] | ((acc: U, value: T, index: number) => U), 864 | fnOrInitial: ((acc: U, value: T, index: number) => U) | U, 865 | initial?: U 866 | ): U | ((array: T[]) => U) { 867 | return arguments.length === 2 868 | ? (array: T[]) => 869 | reduce( 870 | array, 871 | arrayOrFn as (acc: U, value: T, index: number) => U, 872 | fnOrInitial as U 873 | ) 874 | : (arrayOrFn as T[]).reduce( 875 | fnOrInitial as (acc: U, value: T, index: number) => U, 876 | initial as U 877 | ); 878 | } 879 | 880 | /** 881 | * Reduces an array to a single value by applying a function to each element and accumulating the result from right to left. 882 | * 883 | * @param array - The array to operate on 884 | * @param fn - The function to reduce the array with 885 | * @param initial - The initial value to reduce the array with 886 | * 887 | * @example 888 | * ``` 889 | * const x = pipe( 890 | * [1, 2, 3, 4, 5], 891 | * A.reduceRight((acc, cur) => acc + cur, 0) 892 | * ); 893 | * 894 | * assertEquals(x, 15); 895 | * ``` 896 | */ 897 | export function reduceRight( 898 | array: T[], 899 | fn: (acc: U, value: T, index: number) => U, 900 | initial: U 901 | ): U; 902 | export function reduceRight( 903 | fn: (acc: U, value: T, index: number) => U, 904 | initial: U 905 | ): (array: T[]) => U; 906 | export function reduceRight( 907 | arrayOrFn: T[] | ((acc: U, value: T, index: number) => U), 908 | fnOrInitial: ((acc: U, value: T, index: number) => U) | U, 909 | initial?: U 910 | ): U | ((array: T[]) => U) { 911 | return arguments.length === 2 912 | ? (array: T[]) => 913 | reduceRight( 914 | array, 915 | arrayOrFn as (acc: U, value: T, index: number) => U, 916 | fnOrInitial as U 917 | ) 918 | : (arrayOrFn as T[]).reduceRight( 919 | fnOrInitial as (acc: U, value: T, index: number) => U, 920 | initial as U 921 | ); 922 | } 923 | 924 | /** 925 | * Returns a new array with elements that do not satisfy the provided predicate function. 926 | * 927 | * @param array - The array to operate on 928 | * @param predicate - The predicate function to use to filter the array 929 | * 930 | * @example 931 | * ``` 932 | * const x = pipe( 933 | * [1, 2, 3, 4, 5], 934 | * A.reject((x) => x % 2 === 0) 935 | * ); 936 | * 937 | * assertEquals(x, [1, 3, 5]); 938 | * ``` 939 | */ 940 | export function reject(array: T[], predicate: (value: T) => boolean): T[]; 941 | export function reject( 942 | predicate: (value: T) => boolean 943 | ): (array: T[]) => T[]; 944 | export function reject( 945 | arrayOrPredicate: T[] | ((value: T) => boolean), 946 | predicate?: (value: T) => boolean 947 | ): T[] | ((array: T[]) => T[]) { 948 | return arguments.length === 1 949 | ? (array: T[]) => reject(array, arrayOrPredicate as (value: T) => boolean) 950 | : (arrayOrPredicate as T[]).filter((value) => !predicate!(value)); 951 | } 952 | 953 | /** 954 | * Returns a new array with it's elements reversed. 955 | * 956 | * @param array - The array to operate on 957 | * 958 | * @example 959 | * ``` 960 | * const x = pipe([1, 2, 3, 4, 5], A.reverse); 961 | * 962 | * assertEquals(x, [5, 4, 3, 2, 1]); 963 | * ``` 964 | */ 965 | export function reverse(array: T[]): T[] { 966 | return [...array].reverse(); 967 | } 968 | 969 | /** 970 | * Retuns a new array with it's elements shuffled. 971 | * 972 | * @param array - The array to operate on 973 | * 974 | * @example 975 | * ``` 976 | * const x = pipe([1, 2, 3, 4, 5], A.shuffle); 977 | * // x is now a shuffled version of [1, 2, 3, 4, 5] 978 | * ``` 979 | */ 980 | export function shuffle(array: T[]): T[] { 981 | const xs = [...array]; 982 | return xs.reduce((acc, _, i) => { 983 | const randomIndex = Math.floor(Math.random() * (i + 1)); 984 | [acc[i], acc[randomIndex]] = [acc[randomIndex], acc[i]]; 985 | return acc; 986 | }, xs); 987 | } 988 | 989 | /** 990 | * Returns a new array with it's elements sorted. 991 | * 992 | * @param array - The array to operate on 993 | * @param compareFn - The compare function to use to sort the array 994 | * 995 | * @example 996 | * ``` 997 | * const x = pipe( 998 | * [1, 2, 3, 4, 5], 999 | * A.sort((a, b) => b - a) 1000 | * ); 1001 | * 1002 | * const y = pipe( 1003 | * ["b", "a", "c", "e", "d"], 1004 | * A.sort((a, b) => a.localeCompare(b)) 1005 | * ); 1006 | * 1007 | * assertEquals(x, [5, 4, 3, 2, 1]); 1008 | * assertEquals(y, ["a", "b", "c", "d", "e"]); 1009 | * ``` 1010 | */ 1011 | export function sort(array: T[], compareFn?: (a: T, b: T) => number): T[]; 1012 | export function sort(compareFn: (a: T, b: T) => number): (array: T[]) => T[]; 1013 | export function sort( 1014 | arrayOrCompareFn: T[] | ((a: T, b: T) => number), 1015 | compareFn?: (a: T, b: T) => number 1016 | ): T[] | ((array: T[]) => T[]) { 1017 | return arguments.length === 1 1018 | ? (array: T[]) => sort(array, arrayOrCompareFn as (a: T, b: T) => number) 1019 | : [...(arrayOrCompareFn as T[])].sort(compareFn); 1020 | } 1021 | 1022 | /** 1023 | * Returns the sum of all elements in a array of numbers. 1024 | * 1025 | * @param array - The array to operate on 1026 | * 1027 | * @example 1028 | * ``` 1029 | * const x = pipe([1, 2, 3, 4, 5], A.sum); 1030 | * 1031 | * assertEquals(x, 15); 1032 | * ``` 1033 | */ 1034 | export function sum(array: number[]): number { 1035 | return array!.reduce((acc, cur) => acc + cur, 0); 1036 | } 1037 | 1038 | /** 1039 | * Returns an Option of Some type of all elements of the array except the first and None if the array is empty. 1040 | * 1041 | * @param array - The array to operate on 1042 | * 1043 | * @example 1044 | * ``` 1045 | * const x = pipe([1, 2, 3, 4, 5], A.tail, O.unwrapOr([0])); 1046 | * const y = pipe([], A.tail, O.unwrapOr([0])); 1047 | * const z = pipe([1], A.tail, O.unwrapOr([0])); 1048 | * 1049 | * assertEquals(x, [2, 3, 4, 5]); 1050 | * assertEquals(y, [0]); 1051 | * assertEquals(z, []); 1052 | * ``` 1053 | */ 1054 | export function tail(array: T[]): Option { 1055 | return array.length === 0 ? none : some(array.slice(1)); 1056 | } 1057 | 1058 | /** 1059 | * Retuns a new array which is an Option of Some type with n elements taken from the beginning of the array. None is returned if n is negative. If n is greater than the length of the array, the entire array is returned. 1060 | * 1061 | * @param array - The array to operate on 1062 | * @param n - The number of elements to take from the array 1063 | * 1064 | * @example 1065 | * ``` 1066 | * const x = pipe([1, 2, 3, 4, 5], A.take(3), O.unwrapOr([0])); 1067 | * const y = pipe([1, 2, 3, 4, 5], A.take(-1), O.unwrapOr([0])); 1068 | * const z = pipe([1, 2, 3, 4, 5], A.take(10), O.unwrapOr([0])); 1069 | * 1070 | * assertEquals(x, [1, 2, 3]); 1071 | * assertEquals(y, [0]); 1072 | * assertEquals(z, [1, 2, 3, 4, 5]); 1073 | */ 1074 | export function take(array: T[], n: number): Option; 1075 | export function take(n: number): (array: T[]) => Option; 1076 | export function take( 1077 | arrayOrN: T[] | number, 1078 | n?: number 1079 | ): Option | ((array: T[]) => Option) { 1080 | if (arguments.length === 1) { 1081 | return (array: T[]) => take(array, arrayOrN as number); 1082 | } 1083 | 1084 | return n! < 0 ? none : some((arrayOrN as T[]).slice(0, n!)); 1085 | } 1086 | 1087 | /** 1088 | * takeWhile 1089 | * Returns a new array with all elements taken from the beginning of the array until the first element that does not satisfy the predicate. 1090 | * 1091 | * @param array 1092 | * @param predicate 1093 | * 1094 | * @example 1095 | * ``` 1096 | * const x = pipe([1, 2, 3, 4, 5], A.takeWhile((n) => n < 3)); 1097 | * 1098 | * assertEquals(x, [1, 2]); 1099 | * ``` 1100 | */ 1101 | export function takeWhile(array: T[], predicate: (a: T) => boolean): T[]; 1102 | export function takeWhile(predicate: (a: T) => boolean): (array: T[]) => T[]; 1103 | export function takeWhile( 1104 | arrayOrPredicate: T[] | ((a: T) => boolean), 1105 | predicate?: (a: T) => boolean 1106 | ): T[] | ((array: T[]) => T[]) { 1107 | if (arguments.length === 1) { 1108 | return (array: T[]) => 1109 | takeWhile(array, arrayOrPredicate as (a: T) => boolean); 1110 | } 1111 | 1112 | const index = (arrayOrPredicate as T[]).findIndex((x) => !predicate!(x)); 1113 | 1114 | return index === -1 1115 | ? (arrayOrPredicate as T[]) 1116 | : (arrayOrPredicate as T[]).slice(0, index); 1117 | } 1118 | 1119 | /** 1120 | * Retuns an Option of Some type of a tuple with the first element of the array (head) and the rest of the array (tail). None is returned if the array is empty. 1121 | * 1122 | * @param array - The array to operate on 1123 | * 1124 | * @example 1125 | * ``` 1126 | * const x = pipe( 1127 | * [1, 2, 3, 4, 5], 1128 | * A.uncons, 1129 | * O.map(([head, tail]) => [head, tail.length]), 1130 | * O.unwrapOr([0, 0]) 1131 | * ); 1132 | * 1133 | * const y = pipe( 1134 | * [], 1135 | * A.uncons, 1136 | * O.map(([head, tail]) => [head, tail.length]), 1137 | * O.unwrapOr([0, 0]) 1138 | * ); 1139 | * 1140 | * assertEquals(x, [1, 4]); 1141 | * assertEquals(y, [0, 0]); 1142 | * ``` 1143 | */ 1144 | export function uncons(array: T[]): Option { 1145 | return array.length === 0 ? none : some([array[0], array.slice(1)]); 1146 | } 1147 | 1148 | /** 1149 | * Retuns the union of two arrays. 1150 | * 1151 | * @param array - The array to operate on 1152 | * @param other - The array to union with 1153 | * 1154 | * @example 1155 | * ``` 1156 | * const x = pipe([1, 3, 5], A.union([2, 4, 6])); 1157 | * const y = pipe([1, 2, 1, 4], A.union([2, 3, 4])); 1158 | * 1159 | * assertEquals(x, [1, 3, 5, 2, 4, 6]); 1160 | * assertEquals(y, [1, 2, 4, 3]); 1161 | * ``` 1162 | */ 1163 | export function union(array: T[], other: T[]): T[]; 1164 | export function union(other: T[]): (array: T[]) => T[]; 1165 | export function union( 1166 | arrayOrOther: T[] | T[], 1167 | other?: T[] 1168 | ): T[] | ((array: T[]) => T[]) { 1169 | return arguments.length === 1 1170 | ? (array: T[]) => union(array, arrayOrOther as T[]) 1171 | : [...new Set([...arrayOrOther, ...other!])]; 1172 | } 1173 | 1174 | /** 1175 | * Returns a new array with all duplicate elements removed. 1176 | * 1177 | * @param array - The array to operate on 1178 | * 1179 | * @example 1180 | * ``` 1181 | * const x = pipe([1, 2, 3, 4, 5, 1, 2, 3, 4, 5], A.uniq); 1182 | * 1183 | * assertEquals(x, [1, 2, 3, 4, 5]); 1184 | * ``` 1185 | */ 1186 | export function uniq(array: T[]): T[] { 1187 | return [...new Set(array)]; 1188 | } 1189 | 1190 | /** 1191 | * Returns a new array with all duplicate elements removed based on the result of the function. 1192 | * 1193 | * @param array - The array to operate on 1194 | * @param fn - The function to apply to each element of the array 1195 | * 1196 | * @example 1197 | * ``` 1198 | * const x = pipe( 1199 | * [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 1 }, { id: 2 }] 1200 | * A.uniqBy((a) => a.id) 1201 | * ); 1202 | * 1203 | * assertEquals(x, [{ id: 1 }, { id: 2 }, { id: 3 }]); 1204 | * ``` 1205 | */ 1206 | export function uniqBy(array: T[], fn: (a: T) => U): T[]; 1207 | export function uniqBy(fn: (a: T) => U): (array: T[]) => T[]; 1208 | export function uniqBy( 1209 | arrayOrF: T[] | ((a: T) => U), 1210 | fn?: (a: T) => U 1211 | ): T[] | ((array: T[]) => T[]) { 1212 | if (arguments.length === 1) { 1213 | return (array: T[]) => uniqBy(array, arrayOrF as (a: T) => U); 1214 | } 1215 | 1216 | const seen = new Set(); 1217 | 1218 | return (arrayOrF as T[]).filter((a) => { 1219 | const u = fn!(a); 1220 | 1221 | if (seen.has(u)) { 1222 | return false; 1223 | } 1224 | 1225 | seen.add(u); 1226 | return true; 1227 | }); 1228 | } 1229 | 1230 | /** 1231 | * Returns a string of all elements of the array joined by newlines. Has the inverse effect of S.lines() 1232 | * 1233 | * @param array - The array to operate on 1234 | * 1235 | * @example 1236 | * ``` 1237 | * const x = pipe(["a", "b", "c"], A.unlines); 1238 | * const y = pipe(["a", "b", "c"], A.unlines, S.lines); 1239 | * 1240 | * assertEquals(x, "a\nb\nc"); 1241 | * assertEquals(y, ["a", "b", "c"]); 1242 | * ``` 1243 | */ 1244 | export function unlines(array: string[]): string { 1245 | return array.join("\n"); 1246 | } 1247 | 1248 | /** 1249 | * Returns a string of all elements of the array joined by whitespaces. Has the inverse effect of S.words(). 1250 | * 1251 | * @param array - The array to operate on 1252 | * 1253 | * @example 1254 | * ``` 1255 | * const x = pipe(["a", "b", "c"], A.unwords); 1256 | * const y = pipe(["a", "b", "c"], A.unwords, S.words); 1257 | * 1258 | * assertEquals(x, "a b c"); 1259 | * assertEquals(y, ["a", "b", "c"]); 1260 | * ``` 1261 | */ 1262 | export function unwords(array: string[]): string { 1263 | return array.join(" "); 1264 | } 1265 | 1266 | /** 1267 | * Returns a new array of tuples, where the n-th tuple contains the corresponding element from each of the argument arrays. 1268 | * 1269 | * @param array - The array to operate on 1270 | * @param other - The array to zip with 1271 | * 1272 | * @example 1273 | * ``` 1274 | * const x = pipe([1, 2, 3], A.zip(["a", "b", "c"])); 1275 | * 1276 | * assertEquals(x, [[1, "a"], [2, "b"], [3, "c"]]); 1277 | * ``` 1278 | */ 1279 | export function zip(array: T[], other: U[]): readonly [T, U][]; 1280 | export function zip(other: U[]): (array: T[]) => readonly [T, U][]; 1281 | export function zip( 1282 | arrayOrOther: T[] | U[], 1283 | other?: U[] 1284 | ): readonly [T, U][] | ((array: T[]) => readonly [T, U][]) { 1285 | return arguments.length === 1 1286 | ? (array: T[]) => zip(array, arrayOrOther as U[]) 1287 | : arrayOrOther.map((a, i) => [a as T, other![i]]); 1288 | } 1289 | -------------------------------------------------------------------------------- /Array_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts"; 2 | 3 | import * as A from "./Array.ts"; 4 | import * as O from "./Option.ts"; 5 | import * as S from "./String.ts"; 6 | import * as G from "./Typeguards.ts"; 7 | import { pipe } from "./Function.ts"; 8 | 9 | Deno.test("Array - all", () => { 10 | const x = pipe( 11 | [1, 2, 3, 4, 5], 12 | A.all((n) => n > 0) 13 | ); 14 | 15 | const y = A.all([1, 2, 3, 4, 5], (n) => n > 0); 16 | 17 | const z = pipe( 18 | ["hi", "hello", "howdy", "bye", "hey there"], 19 | A.all((str) => str.startsWith("h")) 20 | ); 21 | 22 | assertEquals(x, true); 23 | assertEquals(y, true); 24 | assertEquals(z, false); 25 | }); 26 | 27 | Deno.test("Array - any", () => { 28 | const x = pipe( 29 | [1, 2, 3, 4, 5], 30 | A.any((n) => n < 0) 31 | ); 32 | 33 | const y = A.any([1, 2, 3, 4, 5], (n) => n > 4); 34 | 35 | const z = pipe( 36 | ["hi", "hello", "howdy", "bye", "hey there"], 37 | A.any((str) => str.startsWith("h")) 38 | ); 39 | 40 | assertEquals(x, false); 41 | assertEquals(y, true); 42 | assertEquals(z, true); 43 | }); 44 | 45 | Deno.test("Array - append", () => { 46 | const x = pipe([1, 2, 3, 4, 5], A.append(6)); 47 | 48 | const y = A.append([1, 2, 3, 4, 5], 6); 49 | 50 | const arr = [1, 2, 3, 4, 5]; 51 | const z = A.append(arr, 6); 52 | 53 | assertEquals(x, [1, 2, 3, 4, 5, 6]); 54 | assertEquals(y, [1, 2, 3, 4, 5, 6]); 55 | assertEquals(arr, [1, 2, 3, 4, 5]); 56 | assertEquals(z, [1, 2, 3, 4, 5, 6]); 57 | }); 58 | 59 | Deno.test("Array - at", () => { 60 | const x = pipe([1, 2, 3, 4, 5], A.at(2), O.unwrapOr(0)); 61 | 62 | const y = pipe([1, 2, 3, 4, 5], A.at(6), O.unwrapOr(0)); 63 | 64 | const z = pipe( 65 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 66 | A.at(5), 67 | O.match( 68 | (n) => `yes ${n} is at index 5`, 69 | () => "nope not there" 70 | ) 71 | ); 72 | 73 | assertEquals(x, 3); 74 | assertEquals(y, 0); 75 | assertEquals(z, "yes 6 is at index 5"); 76 | }); 77 | 78 | Deno.test("Array - concat", () => { 79 | const x = pipe([1, 2, 3, 4, 5], A.concat([6, 7, 8, 9, 10])); 80 | 81 | const y = A.concat([1, 2, 3, 4, 5], [6, 7, 8, 9, 10]); 82 | 83 | const z = pipe([6, 7, 8, 9, 10], A.concat([1, 2, 3, 4, 5])); 84 | 85 | assertEquals(x, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 86 | assertEquals(y, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 87 | assertEquals(z, [6, 7, 8, 9, 10, 1, 2, 3, 4, 5]); 88 | }); 89 | 90 | Deno.test("Array - clone", () => { 91 | const x = A.clone([1, 2, 3, 4, 5]); 92 | 93 | assertEquals(x, [1, 2, 3, 4, 5]); 94 | }); 95 | 96 | Deno.test("Array - diff", () => { 97 | const x = pipe([1, 2, 3, 4, 5], A.diff([1, 3, 5])); 98 | 99 | const y = A.diff([1, 2, 3, 4, 5], [1, 3, 5]); 100 | 101 | const z = pipe( 102 | [1, 2, 3, 4, 5], 103 | A.diff([1, 3, 5]), 104 | A.diff([2, 4]), 105 | A.at(0), 106 | O.unwrapOr(0) 107 | ); 108 | 109 | const a = pipe([], A.diff([1, 3, 5])); 110 | 111 | const b = pipe([1, 3, 5], A.diff([2, 4, 6])); 112 | 113 | assertEquals(x, [2, 4]); 114 | assertEquals(y, [2, 4]); 115 | assertEquals(z, 0); 116 | assertEquals(a, []); 117 | assertEquals(b, [1, 3, 5]); 118 | }); 119 | 120 | Deno.test("Array - drop", () => { 121 | const x = pipe([1, 2, 3, 4, 5], A.drop(2), O.unwrapOr([] as number[])); 122 | const y = pipe([1, 2, 3, 4, 5], A.drop(6), O.unwrapOr([] as number[])); 123 | const z = pipe([1, 2, 3, 4, 5], A.drop(-1), O.unwrapOr([] as number[])); 124 | 125 | assertEquals(x, [3, 4, 5]); 126 | assertEquals(y, []); 127 | assertEquals(z, []); 128 | }); 129 | 130 | Deno.test("Array - find", () => { 131 | const x = pipe( 132 | [1, 2, 3, 4, 5], 133 | A.find((n) => n > 3), 134 | O.unwrapOr(0) 135 | ); 136 | 137 | const y = pipe( 138 | [1, 2, 3, 4, 5], 139 | A.find((n) => n < 0), 140 | O.unwrapOr(0) 141 | ); 142 | 143 | assertEquals(x, 4); 144 | assertEquals(y, 0); 145 | }); 146 | 147 | Deno.test("Array - findIndex", () => { 148 | const x = pipe( 149 | [1, 2, 3, 4, 5], 150 | A.findIndex((n) => n > 3), 151 | O.unwrapOr(0) 152 | ); 153 | 154 | const y = pipe( 155 | [1, 2, 3, 4, 5], 156 | A.findIndex((n) => n < 0), 157 | O.unwrapOr(100) 158 | ); 159 | 160 | assertEquals(x, 3); 161 | assertEquals(y, 100); 162 | }); 163 | 164 | Deno.test("Array - flatten", () => { 165 | const x = pipe( 166 | [ 167 | [1, 2, 3], 168 | [4, 5, 6], 169 | [7, 8, 9], 170 | ], 171 | A.flatten, 172 | A.at(4), 173 | O.unwrapOr(0) 174 | ); 175 | 176 | const y = pipe( 177 | [ 178 | [1, 2, 3], 179 | [4, 5, 6], 180 | [7, 8, 9], 181 | ], 182 | A.flatten, 183 | A.at(10), 184 | O.unwrapOr(0) 185 | ); 186 | 187 | const z = pipe([1, 2, [3, 4, [5, 6]], 7, 8], A.flatten); 188 | 189 | assertEquals(x, 5); 190 | assertEquals(y, 0); 191 | assertEquals(z, [1, 2, 3, 4, 5, 6, 7, 8]); 192 | }); 193 | 194 | Deno.test("Array - head", () => { 195 | const x = pipe([1, 2, 3, 4, 5], A.head, O.unwrapOr(0)); 196 | const y = pipe([], A.head, O.unwrapOr(0)); 197 | 198 | assertEquals(x, 1); 199 | assertEquals(y, 0); 200 | }); 201 | 202 | Deno.test("Array - intersection", () => { 203 | const x = pipe([1, 2, 3, 4, 5], A.intersection([1, 3, 5])); 204 | const y = A.intersection([2, 4, 6], [1, 3, 5]); 205 | const z = pipe([1, 2, 1, 1, 3], A.intersection([1])); 206 | const a = A.intersection([1, 2, 1, 1, 3], [1]); 207 | 208 | assertEquals(x, [1, 3, 5]); 209 | assertEquals(y, []); 210 | assertEquals(z, [1]); 211 | assertEquals(a, [1]); 212 | }); 213 | 214 | Deno.test("Array - last", () => { 215 | const x = pipe([1, 2, 3, 4, 5], A.last, O.unwrapOr(0)); 216 | const y = pipe([], A.last, O.unwrapOr(0)); 217 | 218 | assertEquals(x, 5); 219 | assertEquals(y, 0); 220 | }); 221 | 222 | Deno.test("Array - prepend", () => { 223 | const x = pipe([1, 2, 3, 4, 5], A.prepend(0)); 224 | const y = A.prepend(0, [1, 2, 3, 4, 5]); 225 | const z = pipe([1, 2, 3, 4, 5], A.prepend(0), A.prepend(-1)); 226 | 227 | assertEquals(x, [0, 1, 2, 3, 4, 5]); 228 | assertEquals(y, [0, 1, 2, 3, 4, 5]); 229 | assertEquals(z, [-1, 0, 1, 2, 3, 4, 5]); 230 | }); 231 | 232 | Deno.test("Array - reject", () => { 233 | const x = pipe( 234 | [1, 2, 3, 4, 5], 235 | A.reject((n) => n > 3) 236 | ); 237 | 238 | const y = A.reject([1, 2, 3, 4, 5], (n) => n > 3); 239 | 240 | const z = pipe( 241 | [1, 2, 3, 4, 5], 242 | A.reject((n) => n < 0) 243 | ); 244 | 245 | assertEquals(x, [1, 2, 3]); 246 | assertEquals(y, [1, 2, 3]); 247 | assertEquals(z, [1, 2, 3, 4, 5]); 248 | }); 249 | 250 | Deno.test("Array - shuffle", () => { 251 | const x = pipe([1, 2, 3, 4, 5], A.shuffle); 252 | 253 | const y = A.shuffle([1, 2, 3, 4, 5]); 254 | 255 | assertEquals(x.length, 5); 256 | assertEquals(y.length, 5); 257 | }); 258 | 259 | Deno.test("Array - tail", () => { 260 | const x = pipe([1, 2, 3, 4, 5], A.tail, O.unwrapOr([0])); 261 | const y = pipe([1], A.tail, O.unwrapOr([0])); 262 | const z = pipe([], A.tail, O.unwrapOr([0])); 263 | 264 | assertEquals(x, [2, 3, 4, 5]); 265 | assertEquals(y, []); 266 | assertEquals(z, [0]); 267 | }); 268 | 269 | Deno.test("Array - take", () => { 270 | const x = pipe([1, 2, 3, 4, 5], A.take(3), O.unwrapOr([0])); 271 | const y = pipe([1, 2, 3, 4, 5], A.take(-1), O.unwrapOr([0])); 272 | const z = pipe([1, 2, 3, 4, 5], A.take(10), O.unwrapOr([0])); 273 | 274 | assertEquals(x, [1, 2, 3]); 275 | assertEquals(y, [0]); 276 | assertEquals(z, [1, 2, 3, 4, 5]); 277 | }); 278 | 279 | Deno.test("Array - union", () => { 280 | const x = pipe([1, 2, 3, 4, 5], A.union([1, 3, 5])); 281 | const y = A.union([2, 4, 6], [1, 3, 5]); 282 | const z = pipe([1, 2, 1, 1, 3], A.union([1])); 283 | 284 | assertEquals(x, [1, 2, 3, 4, 5]); 285 | assertEquals(y, [2, 4, 6, 1, 3, 5]); 286 | assertEquals(z, [1, 2, 3]); 287 | }); 288 | 289 | Deno.test("Array - uniq", () => { 290 | const x = pipe([1, 2, 3, 4, 5], A.uniq); 291 | const y = A.uniq([1, 1, 1, 1, 1, 1, 1, 1]); 292 | const z = pipe([1, 2, 1, 1, 3], A.uniq); 293 | 294 | assertEquals(x, [1, 2, 3, 4, 5]); 295 | assertEquals(y, [1]); 296 | assertEquals(z, [1, 2, 3]); 297 | }); 298 | 299 | Deno.test("Array - sum", () => { 300 | const x = pipe([1, 2, 3, 4, 5], A.sum); 301 | const y = A.sum([1, 2, 3, 4, 5]); 302 | 303 | assertEquals(x, 15); 304 | assertEquals(y, 15); 305 | }); 306 | 307 | Deno.test("Array - product", () => { 308 | const x = pipe([1, 2, 3, 4, 5], A.product); 309 | const y = A.product([1, 2, 3, 4, 5]); 310 | 311 | assertEquals(x, 120); 312 | assertEquals(y, 120); 313 | }); 314 | 315 | Deno.test("Array - max", () => { 316 | const x = pipe([1, 2, 3, 4, 5], A.max, O.unwrapOr(0)); 317 | const y = O.unwrapOr(A.max([]), 0); 318 | 319 | assertEquals(x, 5); 320 | assertEquals(y, 0); 321 | }); 322 | 323 | Deno.test("Array - min", () => { 324 | const x = pipe([1, 2, 3, 4, 5], A.min, O.unwrapOr(0)); 325 | const y = O.unwrapOr(A.min([]), 0); 326 | 327 | assertEquals(x, 1); 328 | assertEquals(y, 0); 329 | }); 330 | 331 | Deno.test("Array - intersperse", () => { 332 | const x = pipe([1, 2, 3, 4, 5], A.intersperse(0)); 333 | const y = A.intersperse([1, 2, 3, 4, 5], 0); 334 | 335 | assertEquals(x, [1, 0, 2, 0, 3, 0, 4, 0, 5]); 336 | assertEquals(y, [1, 0, 2, 0, 3, 0, 4, 0, 5]); 337 | }); 338 | 339 | Deno.test("Array - map", () => { 340 | const arr = [1, 2, 3, 4, 5]; 341 | 342 | const x = pipe( 343 | arr, 344 | A.map((n, i) => n * i) 345 | ); 346 | 347 | const y = A.map([1, 2, 3, 4, 5], (n) => n + 1); 348 | 349 | const z = pipe( 350 | arr, 351 | A.map((n) => n * 2) 352 | ); 353 | 354 | assertEquals(arr, [1, 2, 3, 4, 5]); 355 | assertEquals(x, [0, 2, 6, 12, 20]); 356 | assertEquals(y, [2, 3, 4, 5, 6]); 357 | assertEquals(z, [2, 4, 6, 8, 10]); 358 | }); 359 | 360 | Deno.test("Array - length", () => { 361 | const x = pipe([1, 2, 3, 4, 5], A.length); 362 | const y = A.length([1, 2, 3, 4, 5]); 363 | 364 | assertEquals(x, 5); 365 | assertEquals(y, 5); 366 | }); 367 | 368 | Deno.test("Array - filter", () => { 369 | const arr = [1, 2, 3, 4, 5]; 370 | 371 | const x = pipe( 372 | arr, 373 | A.filter((n) => n % 2 === 0) 374 | ); 375 | 376 | const y = A.filter(arr, (n) => n % 2 === 0); 377 | 378 | const z = pipe( 379 | arr, 380 | A.filter((_, i) => i % 2 === 0) 381 | ); 382 | 383 | assertEquals(arr, [1, 2, 3, 4, 5]); 384 | assertEquals(x, [2, 4]); 385 | assertEquals(y, [2, 4]); 386 | assertEquals(z, [1, 3, 5]); 387 | }); 388 | 389 | Deno.test("Array - sort", () => { 390 | const arr = [2, 4, 1, 3, 5]; 391 | 392 | const x = pipe( 393 | arr, 394 | A.sort((a, b) => a - b) 395 | ); 396 | 397 | const y = A.sort([1, 2, 3, 4, 5], (a, b) => b - a); 398 | 399 | const z = pipe( 400 | ["d", "a", "e", "b", "c"], 401 | A.sort((a, b) => a.localeCompare(b)) 402 | ); 403 | 404 | assertEquals(arr, [2, 4, 1, 3, 5]); 405 | assertEquals(x, [1, 2, 3, 4, 5]); 406 | assertEquals(y, [5, 4, 3, 2, 1]); 407 | assertEquals(z, ["a", "b", "c", "d", "e"]); 408 | }); 409 | 410 | Deno.test("Array - partition", () => { 411 | const arr = [1, 2, 3, 4, 5]; 412 | 413 | const x = pipe( 414 | arr, 415 | A.partition((n) => n % 2 === 0) 416 | ); 417 | 418 | const y = A.partition(["a", 1, "b", 2, "c"], G.isNumber); 419 | 420 | const z = pipe( 421 | arr, 422 | A.partition((_, i) => i % 2 === 0) 423 | ); 424 | 425 | assertEquals(arr, [1, 2, 3, 4, 5]); 426 | 427 | assertEquals(x, [ 428 | [2, 4], 429 | [1, 3, 5], 430 | ]); 431 | 432 | assertEquals(y, [ 433 | [1, 2], 434 | ["a", "b", "c"], 435 | ]); 436 | 437 | assertEquals(z, [ 438 | [1, 3, 5], 439 | [2, 4], 440 | ]); 441 | }); 442 | 443 | Deno.test("Array - reverse", () => { 444 | const x = pipe([1, 2, 3, 4, 5], A.reverse); 445 | const y = A.reverse([1, 2, 3, 4, 5]); 446 | 447 | assertEquals(x, [5, 4, 3, 2, 1]); 448 | assertEquals(y, [5, 4, 3, 2, 1]); 449 | }); 450 | 451 | Deno.test("Array - reduce", () => { 452 | const arr = [1, 2, 3, 4, 5]; 453 | const x = pipe( 454 | arr, 455 | A.reduce((acc, n) => acc + n, 0) 456 | ); 457 | 458 | const y = A.reduce(arr, (acc, n) => acc + n, 0); 459 | 460 | const z = pipe( 461 | arr, 462 | A.reduce((acc, n, i) => acc + n + i, 0) 463 | ); 464 | 465 | assertEquals(arr, [1, 2, 3, 4, 5]); 466 | assertEquals(x, 15); 467 | assertEquals(y, 15); 468 | assertEquals(z, 25); 469 | }); 470 | 471 | Deno.test("Array - reduceRight", () => { 472 | const arr = [1, 2, 3, 4, 5]; 473 | 474 | const x = pipe( 475 | arr, 476 | A.reduceRight((acc, n) => acc + n, 0) 477 | ); 478 | 479 | const y = A.reduceRight(arr, (acc, n) => acc + n, 0); 480 | 481 | const z = pipe( 482 | arr, 483 | A.reduceRight((acc, n, i) => acc + n + i, 0) 484 | ); 485 | 486 | assertEquals(arr, [1, 2, 3, 4, 5]); 487 | assertEquals(x, 15); 488 | assertEquals(y, 15); 489 | assertEquals(z, 25); 490 | }); 491 | 492 | Deno.test("Array - zip", () => { 493 | const x = pipe([1, 2, 3, 4, 5], A.zip([6, 7, 8, 9, 10])); 494 | 495 | const y = pipe(["a", "b", "c"], A.zip([1, 2, 3, 4, 5])); 496 | 497 | assertEquals(x, [ 498 | [1, 6], 499 | [2, 7], 500 | [3, 8], 501 | [4, 9], 502 | [5, 10], 503 | ]); 504 | 505 | assertEquals(y, [ 506 | ["a", 1], 507 | ["b", 2], 508 | ["c", 3], 509 | ]); 510 | }); 511 | 512 | Deno.test("Array - uncons", () => { 513 | const x = pipe( 514 | [1, 2, 3, 4, 5], 515 | A.uncons, 516 | O.map(([head, tail]) => [head, tail.length]), 517 | O.unwrapOr([0]) 518 | ); 519 | 520 | const y = pipe( 521 | [], 522 | A.uncons, 523 | O.map(([head, tail]) => [head, tail.length]), 524 | O.unwrapOr([0]) 525 | ); 526 | 527 | assertEquals(x, [1, 4]); 528 | assertEquals(y, [0]); 529 | }); 530 | 531 | Deno.test("Array - join", () => { 532 | const x = pipe([1, 2, 3, 4, 5], A.join(", ")); 533 | const y = A.join(["a", "b", "c"], "."); 534 | 535 | assertEquals(x, "1, 2, 3, 4, 5"); 536 | assertEquals(y, "a.b.c"); 537 | }); 538 | 539 | Deno.test("Array - isEmpty", () => { 540 | const x = pipe([1, 2, 3, 4, 5], A.isEmpty); 541 | const y = A.isEmpty([]); 542 | 543 | assertEquals(x, false); 544 | assertEquals(y, true); 545 | }); 546 | 547 | Deno.test("Array - isNonEmpty", () => { 548 | const x = pipe([1, 2, 3, 4, 5], A.isNonEmpty); 549 | const y = A.isNonEmpty([]); 550 | 551 | assertEquals(x, true); 552 | assertEquals(y, false); 553 | }); 554 | 555 | Deno.test("Array - unlines", () => { 556 | const x = pipe(["a", "b", "c"], A.unlines, S.lines); 557 | const y = A.unlines(["hello", "world", "deno"]); 558 | 559 | assertEquals(x, ["a", "b", "c"]); 560 | assertEquals(y, "hello\nworld\ndeno"); 561 | }); 562 | 563 | Deno.test("Array - unwords", () => { 564 | const x = pipe(["a", "b", "c"], A.unwords, S.words); 565 | const y = A.unwords(["hello", "world", "deno"]); 566 | 567 | assertEquals(x, ["a", "b", "c"]); 568 | assertEquals(y, "hello world deno"); 569 | }); 570 | 571 | Deno.test("Array - uniqBy", () => { 572 | const x = pipe( 573 | [{ id: 1 }, { id: 2 }, { id: 1 }, { id: 3 }], 574 | A.uniqBy((a) => a.id) 575 | ); 576 | 577 | const y = A.uniqBy(["a", "b", "c", "a", "b"], (a) => a); 578 | 579 | assertEquals(x, [{ id: 1 }, { id: 2 }, { id: 3 }]); 580 | assertEquals(y, ["a", "b", "c"]); 581 | }); 582 | 583 | Deno.test("Array - dropWhile", () => { 584 | const x = pipe( 585 | [1, 2, 3, 4, 5], 586 | A.dropWhile((n) => n < 3) 587 | ); 588 | 589 | const y = pipe( 590 | [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 2 }], 591 | A.dropWhile((n) => n.id < 3) 592 | ); 593 | 594 | assertEquals(x, [3, 4, 5]); 595 | assertEquals(y, [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 2 }]); 596 | }); 597 | 598 | Deno.test("Array - takeWhile", () => { 599 | const x = pipe( 600 | [1, 2, 3, 4, 5], 601 | A.takeWhile((n) => n < 3) 602 | ); 603 | 604 | const y = pipe( 605 | [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 2 }], 606 | A.takeWhile((n) => n.id < 3) 607 | ); 608 | 609 | assertEquals(x, [1, 2]); 610 | assertEquals(y, [{ id: 1 }, { id: 2 }]); 611 | }); 612 | 613 | Deno.test("Array - filterMap", () => { 614 | const x = pipe( 615 | [1, 2, 3, 4, 5], 616 | A.filterMap((n) => (n % 2 === 0 ? O.some(n) : O.none)) 617 | ); 618 | 619 | const y = pipe( 620 | ["a", "b", "c", "d", "e"], 621 | A.filterMap((n, i) => (i % 2 === 0 ? O.some(n) : O.none)) 622 | ); 623 | 624 | const z = pipe( 625 | ["a", "b", "c", "d", "e"], 626 | A.filterMap((n) => (n === "c" ? O.some(n) : O.none)) 627 | ); 628 | 629 | assertEquals(x, [2, 4]); 630 | assertEquals(y, ["a", "c", "e"]); 631 | assertEquals(z, ["c"]); 632 | }); 633 | -------------------------------------------------------------------------------- /Function.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from "./functions/pipe.ts"; 2 | import { compose } from "./functions/compose.ts"; 3 | 4 | export { pipe, compose }; 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Shoubhit Dash 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. 22 | -------------------------------------------------------------------------------- /Number.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a number is even. 3 | * 4 | * @param n - The number to check 5 | * 6 | * @example 7 | * ``` 8 | * const x = pipe(2, N.even); 9 | * const y = pipe(3, N.even); 10 | * 11 | * assertEquals(x, true); 12 | * assertEquals(y, false); 13 | * ``` 14 | */ 15 | export function even(n: number): boolean { 16 | return n % 2 === 0; 17 | } 18 | 19 | /** 20 | * Returns the greatest common divisor of two numbers. 21 | * 22 | * @param a - The first number 23 | * @param b - The second number 24 | * 25 | * @example 26 | * ``` 27 | * const x = N.gcd(2, 3); 28 | * const y = N.gcd(8, -12); 29 | * 30 | * assertEquals(x, 1); 31 | * assertEquals(y, 4); 32 | * ``` 33 | */ 34 | export function gcd(a: number, b: number): number; 35 | export function gcd(b: number): (a: number) => number; 36 | export function gcd(a: number, b?: number): number | ((a: number) => number) { 37 | if (b === undefined) return (b: number) => gcd(a, b); 38 | 39 | if (b === 0) return a; 40 | 41 | return Math.abs(gcd(b, a % b)); 42 | } 43 | 44 | /** 45 | * Checks if a number is odd. 46 | * 47 | * @param n - The number to check 48 | * 49 | * @example 50 | * ``` 51 | * const x = pipe(2, N.odd); 52 | * const y = pipe(3, N.odd); 53 | * 54 | * assertEquals(x, false); 55 | * assertEquals(y, true); 56 | * ``` 57 | */ 58 | export function odd(n: number): boolean { 59 | return n % 2 !== 0; 60 | } 61 | 62 | /** 63 | * Returns the least common multiple of two numbers. 64 | * 65 | * @param a - The first number 66 | * @param b - The second number 67 | * 68 | * @example 69 | * ``` 70 | * const x = N.lcm(2, 3); 71 | * const y = N.lcm(8, 12); 72 | * 73 | * assertEquals(x, 6); 74 | * assertEquals(y, 24); 75 | * ``` 76 | */ 77 | export function lcm(a: number, b: number): number; 78 | export function lcm(b: number): (a: number) => number; 79 | export function lcm(a: number, b?: number): number | ((a: number) => number) { 80 | if (b === undefined) return (b: number) => lcm(a, b); 81 | 82 | return Math.abs(a * b) / gcd(a, b); 83 | } 84 | -------------------------------------------------------------------------------- /Number_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts"; 2 | import { pipe } from "./functions/pipe.ts"; 3 | 4 | import * as N from "./Number.ts"; 5 | 6 | Deno.test("Number - even", () => { 7 | assertEquals(pipe(2, N.even), true); 8 | assertEquals(pipe(3, N.even), false); 9 | }); 10 | 11 | Deno.test("Number - gcd", () => { 12 | const x = N.gcd(2, 3); 13 | const y = N.gcd(8)(12); 14 | const z = N.gcd(8)(-12); 15 | 16 | assertEquals(x, 1); 17 | assertEquals(y, 4); 18 | assertEquals(z, 4); 19 | }); 20 | 21 | Deno.test("Number - odd", () => { 22 | assertEquals(pipe(2, N.odd), false); 23 | assertEquals(pipe(3, N.odd), true); 24 | }); 25 | 26 | Deno.test("Number - lcm", () => { 27 | assertEquals(N.lcm(2, 3), 6); 28 | assertEquals(N.lcm(8, 12), 24); 29 | assertEquals(N.lcm(8, -12), 24); 30 | }); 31 | -------------------------------------------------------------------------------- /Option.ts: -------------------------------------------------------------------------------- 1 | import { isFunction, isObject, isString } from "./Typeguards.ts"; 2 | 3 | export type Some = { readonly type: "some"; readonly value: T }; 4 | export type None = { readonly type: "none" }; 5 | 6 | export type Option = Some | None; 7 | 8 | /** 9 | * Checks if a value is an Option. 10 | * 11 | * @param value - The value to check 12 | * 13 | * @example 14 | * ``` 15 | * const x = O.fromNullable(3); 16 | * const y = O.fromNullable(null); 17 | * const z = 3; 18 | * 19 | * assertEquals(O.isOption(x), true); 20 | * assertEquals(O.isOption(y), true); 21 | * assertEquals(O.isOption(z), false); 22 | * ``` 23 | */ 24 | export function isOption(value: unknown): value is Option { 25 | return ( 26 | isObject(value) && 27 | typeof (value as { type?: string }).type === "string" && 28 | ["some", "none"].includes((value as { type: string }).type) 29 | ); 30 | } 31 | 32 | /** 33 | * Checks if an Option is of type Some. 34 | * 35 | * @param option - The Option to check 36 | * 37 | * @example 38 | * ``` 39 | * const x = O.fromNullable(3); 40 | * const y = O.fromNullable(null); 41 | * 42 | * assertEquals(O.isSome(x), true); 43 | * assertEquals(O.isSome(y), false); 44 | * ``` 45 | */ 46 | export function isSome(option: Option): option is Some { 47 | return option.type === "some"; 48 | } 49 | 50 | /** 51 | * Checks if an Option is of type None. 52 | * 53 | * @param option - The Option to check 54 | * 55 | * @example 56 | * ``` 57 | * const x = O.fromNullable(3); 58 | * const y = O.fromNullable(null); 59 | * 60 | * assertEquals(O.isNone(x), false); 61 | * assertEquals(O.isNone(y), true); 62 | * ``` 63 | */ 64 | export function isNone(option: Option): option is None { 65 | return option.type === "none"; 66 | } 67 | 68 | /** 69 | * Checks if an Option is of type None. 70 | * 71 | * @param value - The value to be converted to a Some type 72 | * 73 | * @example 74 | * ``` 75 | * const someThree = O.some(3); 76 | * const someNull = O.some(null); 77 | * ``` 78 | */ 79 | export function some(value: T): Option { 80 | return { type: "some", value }; 81 | } 82 | 83 | /** 84 | * Constant to represent a value of None type. 85 | */ 86 | export const none: Option = { type: "none" }; 87 | 88 | /** 89 | * Converts a value to an Option. If the value is null or undefined, it will be converted to a None type. Otherwise, it will be converted to a Some type. 90 | * 91 | * @param value - The value to be converted to an Option 92 | * 93 | * 94 | * @example 95 | * ``` 96 | * const x = O.fromNullable(3); 97 | * const y = O.fromNullable(null); 98 | * 99 | * assertEquals(O.isSome(x), true); 100 | * assertEquals(O.isNone(y), true); 101 | * ``` 102 | */ 103 | export function fromNullable( 104 | value: T | null | undefined 105 | ): Option> { 106 | return value === null || value === undefined 107 | ? none 108 | : some(value as NonNullable); 109 | } 110 | 111 | /** 112 | * Converts a value to an Option. If the value is falsy, it will be converted to a None type. Otherwise, it will be converted to a Some type. 113 | * 114 | * @param value - The value to be converted to an Option 115 | * 116 | * @example 117 | * ``` 118 | * const x = O.fromFalsy(3); 119 | * const y = O.fromFalsy(""); 120 | * 121 | * assertEquals(O.isSome(x), true); 122 | * assertEquals(O.isNone(y), true); 123 | * ``` 124 | */ 125 | export function fromFalsy(value: T): Option> { 126 | return value ? some(value as NonNullable) : none; 127 | } 128 | 129 | /** 130 | * Returns a Some if the function does not throw an error. Otherwise, it returns a None. Direct replacement of try/catch. 131 | * 132 | * @param fn - The function to be executed 133 | * 134 | * @example 135 | * ``` 136 | * const x = O.fromException(() => 3); 137 | * const y = O.fromException(() => { throw new Error("error lol") }); 138 | * 139 | * assertEquals(O.isSome(x), true); 140 | * assertEquals(O.isNone(y), true); 141 | * ``` 142 | */ 143 | export function fromException(fn: () => T): Option { 144 | try { 145 | return some(fn()); 146 | } catch (_error) { 147 | return none; 148 | } 149 | } 150 | 151 | /** 152 | * Unwraps an Option, yielding the content of a Some. If the value is None, it will throw an error with the provided message. 153 | * 154 | * @param option - The option to be unwrapped 155 | * @param message - Error message if option is of None type 156 | * 157 | * @example 158 | * ``` 159 | * const x = pipe( 160 | * O.fromNullable(3), 161 | * O.expect("error lol") 162 | * ); // 3 163 | * 164 | * const y = pipe( 165 | * O.fromNullable(null), 166 | * O.expect("error lol") 167 | * ); // throws an Error with the provided message 168 | * ``` 169 | */ 170 | export function expect(option: Option, message: string): T; 171 | export function expect(message: string): (option: Option) => T; 172 | export function expect( 173 | optionOrMessage: Option | string, 174 | message?: string 175 | ): T | ((option: Option) => T) { 176 | if (isString(optionOrMessage)) { 177 | const message = optionOrMessage; 178 | 179 | return (option) => expect(option, message); 180 | } else { 181 | const option = optionOrMessage; 182 | 183 | if (isSome(option)) { 184 | return option.value; 185 | } else { 186 | throw new Error(message); 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * Unwraps an Option, yielding the content of a Some. If the value is None, it will throw an error. 193 | * 194 | * @param option - The option to be unwrapped 195 | * 196 | * @example 197 | * ``` 198 | * const x = pipe( 199 | * O.fromNullable(3), 200 | * O.unwrap() 201 | * ); // 3 202 | * 203 | * const y = pipe( 204 | * O.fromNullable(null), 205 | * O.unwrap() 206 | * ); // throws an Error with the provided message 207 | * ``` 208 | */ 209 | export function unwrap(option: Option): T; 210 | export function unwrap(): (option: Option) => T; 211 | export function unwrap(option?: Option): T | ((option: Option) => T) { 212 | if (isOption(option)) { 213 | return expect(option, "Cannot unwrap a None value"); 214 | } else { 215 | return (option) => unwrap(option); 216 | } 217 | } 218 | 219 | /** 220 | * Unwraps an Option, yielding the content of a Some. If the value is None, it will return the provided default value. 221 | * 222 | * @param option - The option to be unwrapped 223 | * @param defaultValue - The default value to be returned if the option is of None type 224 | * 225 | * @example 226 | * ``` 227 | * const x = pipe( 228 | * O.fromNullable(null), 229 | * O.unwrapOr(4) 230 | * ); 231 | * 232 | * assertEquals(x, 4); 233 | * ``` 234 | */ 235 | export function unwrapOr(option: Option, defaultValue: T): T; 236 | export function unwrapOr(defaultValue: T): (option: Option) => T; 237 | export function unwrapOr( 238 | optionOrValue: Option | T, 239 | defaultValue?: T 240 | ): T | ((option: Option) => T) { 241 | if (isOption(optionOrValue)) { 242 | const option = optionOrValue; 243 | 244 | return isSome(option) ? option.value : defaultValue!; 245 | } else { 246 | const value = optionOrValue; 247 | 248 | return (option) => (isSome(option) ? option.value : value); 249 | } 250 | } 251 | 252 | /** 253 | * Unwraps an Option, yielding the content of a Some. If the value is None, it will return the result of calling the provided function. 254 | * 255 | * @param option - The option to be unwrapped 256 | * @param fn - The function to be called if the option is of None type 257 | * 258 | * @example 259 | * ``` 260 | * const x = pipe( 261 | * O.fromNullable(null), 262 | * O.unwrapOrElse(() => 4) 263 | * ); 264 | * 265 | * assertEquals(x, 4); 266 | * ``` 267 | */ 268 | export function unwrapOrElse(option: Option, fn: () => T): T; 269 | export function unwrapOrElse(fn: () => T): (option: Option) => T; 270 | export function unwrapOrElse( 271 | optionOrFn: Option | (() => T), 272 | fn?: () => T 273 | ): T | ((option: Option) => T) { 274 | if (isFunction(optionOrFn)) { 275 | const fn = optionOrFn; 276 | 277 | return (option) => (isSome(option) ? option.value : fn()); 278 | } else { 279 | const option = optionOrFn; 280 | 281 | return isSome(option) ? option.value : fn!(); 282 | } 283 | } 284 | 285 | /** 286 | * Maps an Option by applying a function to a contained Some value, leaving a None value untouched. 287 | * 288 | * @param option - The option to be mapped 289 | * @param fn - The function to be applied to the contained value 290 | * 291 | * @example 292 | * ``` 293 | * const x = pipe( 294 | * O.fromNullable(3), 295 | * O.map((n) => n + 1), 296 | * O.unwrapOr(0) 297 | * ); 298 | * 299 | * const y = pipe( 300 | * O.fromNullable(null), 301 | * O.map((n) => n + 1) 302 | * O.unwrapOr(0) 303 | * ); 304 | * 305 | * assertEquals(x, 4); 306 | * assertEquals(y, 0); 307 | * ``` 308 | */ 309 | export function map( 310 | option: Option, 311 | fn: (value: T) => NonNullable 312 | ): Option; 313 | export function map( 314 | fn: (value: T) => NonNullable 315 | ): (option: Option) => Option; 316 | export function map( 317 | optionOrFn: Option | ((value: T) => NonNullable), 318 | fn?: (value: T) => NonNullable 319 | ): Option | ((option: Option) => Option) { 320 | if (isFunction(optionOrFn)) { 321 | const mapFn = optionOrFn; 322 | 323 | return (option) => map(option, mapFn); 324 | } else { 325 | const option = optionOrFn; 326 | 327 | if (isSome(option)) { 328 | return some( 329 | fn ? fn(option.value) : (option.value as unknown as NonNullable) 330 | ); 331 | } else { 332 | return none; 333 | } 334 | } 335 | } 336 | 337 | /** 338 | * Maps an Option by applying a function that returns an Option to a contained Some value, leaving the None value untouched. 339 | * 340 | * @param option - The option to be mapped 341 | * @param fn - The function to be applied to the contained value 342 | * 343 | * @example 344 | * ``` 345 | * const x = pipe( 346 | * O.fromNullable("hi"), 347 | * O.flatMap((s) => { 348 | * return s.at(0) === "h" ? O.some("bye") : O.none 349 | * }), 350 | * O.unwrapOr("no") 351 | * ); 352 | * 353 | * const y = pipe( 354 | * O.fromNullable("hi"), 355 | * O.flatMap((s) => { 356 | * return s.at(0) === "a" ? O.some("bye") : O.none 357 | * }), 358 | * O.unwrapOr("no") 359 | * ); 360 | * 361 | * assertEquals(x, "bye"); 362 | * assertEquals(y, "no"); 363 | * ``` 364 | */ 365 | export function flatMap( 366 | option: Option, 367 | fn: (value: T) => Option 368 | ): Option; 369 | export function flatMap( 370 | fn: (value: T) => Option 371 | ): (option: Option) => Option; 372 | export function flatMap( 373 | optionOrFn: Option | ((value: T) => Option), 374 | fn?: (value: T) => Option 375 | ): Option | ((option: Option) => Option) { 376 | if (isFunction(optionOrFn)) { 377 | const flatMapFn = optionOrFn; 378 | 379 | return (option) => flatMap(option, flatMapFn); 380 | } else { 381 | const option = optionOrFn; 382 | 383 | if (isSome(option)) { 384 | return fn ? fn(option.value) : (option.value as unknown as Option); 385 | } else { 386 | return none; 387 | } 388 | } 389 | } 390 | 391 | /** 392 | * Allows for pattern matching on an Option value. If the value is Some, the first function will be called with the value. If the value is None, the second function will be called. 393 | * 394 | * @param option - The option to be matched 395 | * @param someFn - The function to be called if the option is of Some type 396 | * @param noneFn - The function to be called if the option is of None type 397 | * 398 | * @example 399 | * ``` 400 | * const x = pipe( 401 | * O.fromNullable(3), 402 | * O.match( 403 | * (n) => n + 1, 404 | * () => 0 405 | * ) 406 | * ); 407 | * 408 | * assertEquals(x, 4); 409 | * ``` 410 | */ 411 | export function match( 412 | option: Option, 413 | someFn: (value: T) => NonNullable, 414 | noneFn: () => U 415 | ): U; 416 | export function match( 417 | someFn: (value: T) => NonNullable, 418 | noneFn: () => U 419 | ): (option: Option) => U; 420 | export function match( 421 | optionOrSomeFn: Option | ((value: T) => NonNullable), 422 | someFnOrNoneFn?: (value: T) => NonNullable | (() => U), 423 | noneFn?: () => U 424 | ): U | ((option: Option) => U) { 425 | if (isFunction(optionOrSomeFn)) { 426 | const someFn = optionOrSomeFn; 427 | const noneFn = someFnOrNoneFn as () => U; 428 | 429 | return (option) => match(option, someFn, noneFn); 430 | } else { 431 | const option = optionOrSomeFn; 432 | const someFn = someFnOrNoneFn as (value: T) => NonNullable; 433 | 434 | if (isSome(option)) { 435 | return someFn(option.value); 436 | } else { 437 | return noneFn ? (noneFn() as U) : (none as unknown as U); 438 | } 439 | } 440 | } 441 | 442 | /** 443 | * Allows running a side effect on an Option if it is a Some type. 444 | * 445 | * @param option - The option to be tapped 446 | * @param fn - The function to be called if the option is of Some type 447 | * 448 | * @example 449 | * ``` 450 | * let x = 0; 451 | * let y = 0; 452 | * 453 | * pipe( 454 | * O.some(1), 455 | * O.tap((n) => { 456 | * x = n; 457 | * }) 458 | * ); 459 | * 460 | * pipe( 461 | * O.none, 462 | * O.tap((n) => { 463 | * y = n; 464 | * }) 465 | * ); 466 | * 467 | * assertEquals(x, 1); 468 | * assertEquals(y, 0); 469 | * ``` 470 | */ 471 | export function tap(option: Option, fn: (value: T) => void): Option; 472 | export function tap( 473 | fn: (value: T) => void 474 | ): (option: Option) => Option; 475 | export function tap( 476 | optionOrFn: Option | ((value: T) => void), 477 | fn?: (value: T) => void 478 | ): Option | ((option: Option) => Option) { 479 | if (isFunction(optionOrFn)) { 480 | const tapFn = optionOrFn; 481 | 482 | return (option) => tap(option, tapFn); 483 | } else { 484 | const option = optionOrFn; 485 | 486 | if (isSome(option)) { 487 | fn ? fn(option.value) : option.value; 488 | } 489 | 490 | return option; 491 | } 492 | } 493 | 494 | /** 495 | * Combines two Options into a single Option of a tuple containing their values if they are a Some type. 496 | * 497 | * @param option - First option to be zipped 498 | * @param other - Second option to be zipped 499 | * 500 | * @example 501 | * ``` 502 | * const x = pipe( 503 | * O.fromNullable("hello"), 504 | * O.zip(O.fromNullable("world")), 505 | * O.expect("error") 506 | * ); 507 | * 508 | * assertEquals(x, ["hello", "world"]) 509 | * ``` 510 | */ 511 | export function zip(option: Option, other: Option): Option<[T, U]>; 512 | export function zip( 513 | other: Option 514 | ): (option: Option) => Option<[T, U]>; 515 | export function zip( 516 | optionOrOther: Option | Option, 517 | other?: Option 518 | ): Option<[T, U]> | ((option: Option) => Option<[T, U]>) { 519 | const zipOptions = (opt1: Option, opt2: Option): Option<[T, U]> => { 520 | if (isSome(opt1) && isSome(opt2)) { 521 | return some([opt1.value, opt2.value]); 522 | } else { 523 | return none; 524 | } 525 | }; 526 | 527 | if (other === null || other === undefined) { 528 | return (option: Option) => 529 | zipOptions(option, optionOrOther as Option); 530 | } else { 531 | return zipOptions(optionOrOther as Option, other); 532 | } 533 | } 534 | -------------------------------------------------------------------------------- /Option_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts"; 2 | 3 | import * as O from "./Option.ts"; 4 | import { pipe } from "./Function.ts"; 5 | 6 | Deno.test("Option: some", () => { 7 | const some = O.some(1); 8 | 9 | assertEquals(some, { type: "some", value: 1 }); 10 | }); 11 | 12 | Deno.test("Option: none", () => { 13 | const none = O.none; 14 | 15 | assertEquals(none, { type: "none" }); 16 | }); 17 | 18 | Deno.test("Option: isOption", () => { 19 | assertEquals(O.isOption(O.some(1)), true); 20 | assertEquals(O.isOption(O.none), true); 21 | assertEquals(O.isOption(1), false); 22 | assertEquals(O.isOption("1"), false); 23 | assertEquals(O.isOption(true), false); 24 | assertEquals(O.isOption(undefined), false); 25 | }); 26 | 27 | Deno.test("Option: isSome", () => { 28 | assertEquals(O.isSome(O.some(1)), true); 29 | assertEquals(O.isSome(O.none), false); 30 | }); 31 | 32 | Deno.test("Option: isNone", () => { 33 | assertEquals(O.isNone(O.some(1)), false); 34 | assertEquals(O.isNone(O.none), true); 35 | }); 36 | 37 | Deno.test("Option: fromNullable", () => { 38 | assertEquals(O.fromNullable(1), O.some(1)); 39 | assertEquals(O.fromNullable(null), O.none); 40 | assertEquals(O.fromNullable(undefined), O.none); 41 | assertEquals(O.fromNullable(false), O.some(false)); 42 | assertEquals(O.fromNullable(""), O.some("")); 43 | }); 44 | 45 | Deno.test("Option: fromFalsy", () => { 46 | assertEquals(O.fromFalsy(1), O.some(1)); 47 | assertEquals(O.fromFalsy(null), O.none); 48 | assertEquals(O.fromFalsy(undefined), O.none); 49 | assertEquals(O.fromFalsy(false), O.none); 50 | assertEquals(O.fromFalsy(""), O.none); 51 | }); 52 | 53 | Deno.test("Option: fromException", () => { 54 | const throwIfNotOne = (x: number) => { 55 | if (x !== 1) { 56 | throw new Error("x is not 1"); 57 | } 58 | 59 | return x; 60 | }; 61 | 62 | const x = O.fromException(() => throwIfNotOne(1)); 63 | const y = O.fromException(() => throwIfNotOne(2)); 64 | 65 | assertEquals(O.isSome(x), true); 66 | assertEquals(O.isNone(y), true); 67 | }); 68 | 69 | Deno.test("Option: expect", () => { 70 | assertEquals(O.expect(O.some(1), "error"), 1); 71 | assertEquals(O.expect("error")(O.some(1)), 1); 72 | }); 73 | 74 | Deno.test("Option: unwrap", () => { 75 | assertEquals(pipe(O.some(1), O.unwrap()), 1); 76 | assertEquals(O.unwrap(O.some(1)), 1); 77 | }); 78 | 79 | Deno.test("Option: unwrapOr", () => { 80 | assertEquals(O.unwrapOr(O.some(1), 2), 1); 81 | assertEquals(O.unwrapOr(O.none, 2), 2); 82 | }); 83 | 84 | Deno.test("Option: unwrapOrElse", () => { 85 | assertEquals( 86 | O.unwrapOrElse(O.some(1), () => 2), 87 | 1 88 | ); 89 | assertEquals( 90 | O.unwrapOrElse(O.none, () => 2), 91 | 2 92 | ); 93 | }); 94 | 95 | Deno.test("Option: map", () => { 96 | assertEquals( 97 | pipe( 98 | O.some(1), 99 | O.map((x) => x + 1) 100 | ), 101 | O.some(2) 102 | ); 103 | assertEquals( 104 | pipe( 105 | O.none, 106 | O.map((x) => x + 1) 107 | ), 108 | O.none 109 | ); 110 | }); 111 | 112 | Deno.test("Option: flatMap", () => { 113 | assertEquals( 114 | pipe( 115 | O.some(1), 116 | O.flatMap((x) => O.some(x + 1)) 117 | ), 118 | O.some(2) 119 | ); 120 | assertEquals( 121 | pipe( 122 | O.none, 123 | O.flatMap((x) => O.some(x + 1)) 124 | ), 125 | O.none 126 | ); 127 | }); 128 | 129 | Deno.test("Option: match", () => { 130 | assertEquals( 131 | pipe( 132 | O.some(1), 133 | O.match( 134 | (x) => x + 1, 135 | () => 0 136 | ) 137 | ), 138 | 2 139 | ); 140 | assertEquals( 141 | pipe( 142 | O.none, 143 | O.match( 144 | (x) => x + 1, 145 | () => 0 146 | ) 147 | ), 148 | 0 149 | ); 150 | }); 151 | 152 | Deno.test("Option: zip", () => { 153 | assertEquals(pipe(O.some(1), O.zip(O.none)), O.none); 154 | assertEquals(pipe(O.none, O.zip(O.some(1))), O.none); 155 | 156 | assertEquals(pipe(O.some(1), O.zip(O.some(2))), O.some([1, 2])); 157 | 158 | const x = pipe( 159 | O.fromNullable("hello"), 160 | O.zip(O.fromNullable("world")), 161 | O.expect("error") 162 | ); 163 | 164 | assertEquals(x, ["hello", "world"]); 165 | }); 166 | Deno.test("Option - tap", () => { 167 | let x = 0; 168 | pipe( 169 | O.some(1), 170 | O.tap(() => { 171 | x = 1; 172 | }) 173 | ); 174 | 175 | assertEquals(x, 1); 176 | 177 | let y = 0; 178 | pipe( 179 | O.none, 180 | O.tap(() => { 181 | y = 1; 182 | }) 183 | ); 184 | 185 | assertEquals(y, 0); 186 | }); 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fp lib wip 2 | on a break cause exams, will be in back in development soon 3 | -------------------------------------------------------------------------------- /String.ts: -------------------------------------------------------------------------------- 1 | import { none, Option, some } from "./Option.ts"; 2 | 3 | /** 4 | * Returns a string with the suffix appended at the end of the string. 5 | * 6 | * @param str - String to append to 7 | * @param suffix - String to append 8 | * 9 | * @example 10 | * ``` 11 | * const x = pipe( 12 | * "Hello", 13 | * S.append(" "), 14 | * S.append("World!"), 15 | * ); 16 | * 17 | * assertEquals(x, "Hello World!"); 18 | * ``` 19 | */ 20 | export function append(str: string, suffix: string): string; 21 | export function append(suffix: string): (str: string) => string; 22 | export function append( 23 | strOrSuffix: string, 24 | suffix?: string 25 | ): string | ((str: string) => string) { 26 | if (arguments.length === 1) { 27 | return (_str: string) => append(_str, strOrSuffix); 28 | } 29 | 30 | return strOrSuffix + suffix; 31 | } 32 | 33 | /** 34 | * Returns a new string with the first character capitalized. 35 | * 36 | * @param str - String to capitalize 37 | * 38 | * @example 39 | * ``` 40 | * const x = pipe("hello", S.capitalize); 41 | * 42 | * assertEquals(x, "Hello"); 43 | * ``` 44 | */ 45 | export function capitalize(str: string): string { 46 | return str.charAt(0).toUpperCase() + str.slice(1); 47 | } 48 | 49 | /** 50 | * Returns a new string with all the words capitalized. 51 | * 52 | * @param str - String to capitalize all words 53 | * 54 | * @example 55 | * ``` 56 | * const x = pipe("hello world", S.capitalizeAll); 57 | * 58 | * assertEquals(x, "Hello World"); 59 | * ``` 60 | */ 61 | export function capitalizeAll(str: string): string { 62 | return str.split(" ").map(capitalize).join(" "); 63 | } 64 | 65 | /** 66 | * Returns an Option of Some type of the character at the given index or None if the index is out of bounds. 67 | * 68 | * @param str - String to check 69 | * @param n - Index of the character to return 70 | * 71 | * @example 72 | * ``` 73 | * const x = pipe("hello", S.charAt(0), O.unwrapOr("nope")); 74 | * const y = pipe("hello", S.charAt(12), O.unwrapOr("nope")); 75 | * 76 | * assertEquals(x, "h"); 77 | * assertEquals(y, "nope"); 78 | * ``` 79 | */ 80 | export function charAt(str: string, n: number): Option; 81 | export function charAt(n: number): (str: string) => Option; 82 | export function charAt( 83 | stringOrN: string | number, 84 | n?: number 85 | ): Option | ((str: string) => Option) { 86 | if (arguments.length === 1) { 87 | return (str: string) => charAt(str, stringOrN as number); 88 | } 89 | 90 | return n! >= 0 && n! < (stringOrN as string).length 91 | ? some((stringOrN as string).charAt(n!)) 92 | : none; 93 | } 94 | 95 | /** 96 | * Returns true if the string ends with the given substring or false otherwise. 97 | * 98 | * @param str - String to check 99 | * @param substr - String to check if it is at the end of the string 100 | * 101 | * @example 102 | * ``` 103 | * const x = pipe("Hello World!", S.endsWith("!")); 104 | * const y = pipe("Hello World!", S.endsWith("!!")); 105 | * 106 | * assertEquals(x, true); 107 | * assertEquals(y, false); 108 | * ``` 109 | */ 110 | export function endsWith(str: string, substr: string): boolean; 111 | export function endsWith(substr: string): (str: string) => boolean; 112 | export function endsWith( 113 | strOrSuffix: string, 114 | substr?: string 115 | ): boolean | ((str: string) => boolean) { 116 | if (arguments.length === 1) { 117 | return (_str: string) => endsWith(_str, strOrSuffix); 118 | } 119 | 120 | return strOrSuffix.endsWith(substr!); 121 | } 122 | 123 | /** 124 | * Returns an Option of Some type of the first character of the string or None if the string is empty. 125 | * 126 | * @param str - String to check 127 | * 128 | * @example 129 | * ``` 130 | * const x = pipe("hello", S.head, O.unwrapOr("nope")); 131 | * const y = pipe("", S.head, O.unwrapOr("nope")); 132 | * 133 | * assertEquals(x, "h"); 134 | * assertEquals(y, "nope"); 135 | * ``` 136 | */ 137 | export function head(str: string): Option { 138 | return str === "" ? none : some(str[0]); 139 | } 140 | 141 | /** 142 | * Returns true if the string contains the given substring or false otherwise. 143 | * 144 | * @param str - String to check 145 | * @param substr - String to check if it is in the string 146 | * 147 | * @example 148 | * ``` 149 | * const x = pipe("hello", S.includes("hell")); 150 | * const y = pipe("hello", S.includes("!")); 151 | * 152 | * assertEquals(x, true); 153 | * assertEquals(y, false); 154 | * ``` 155 | */ 156 | export function includes(str: string, substr: string): boolean; 157 | export function includes(substr: string): (str: string) => boolean; 158 | export function includes( 159 | strOrSuffix: string, 160 | substr?: string 161 | ): boolean | ((str: string) => boolean) { 162 | if (arguments.length === 1) { 163 | return (_str: string) => includes(_str, strOrSuffix); 164 | } 165 | 166 | return strOrSuffix.includes(substr!); 167 | } 168 | 169 | /** 170 | * Returns true if the string is empty or false otherwise. 171 | * 172 | * @param str - String to check 173 | * 174 | * @example 175 | * ``` 176 | * const x = pipe("hello", S.isEmpty); 177 | * const y = pipe("", S.isEmpty); 178 | * 179 | * assertEquals(x, false); 180 | * assertEquals(y, true); 181 | * ``` 182 | */ 183 | export function isEmpty(str: string): boolean { 184 | return str === ""; 185 | } 186 | 187 | /** 188 | * Returns true if the string is not empty or false otherwise. 189 | * 190 | * @param str - String to check 191 | * 192 | * @example 193 | * ``` 194 | * const x = pipe("hello", S.isNonEmpty); 195 | * const y = pipe("", S.isNonEmpty); 196 | * 197 | * assertEquals(x, true); 198 | * assertEquals(y, false); 199 | * ``` 200 | */ 201 | export function isNonEmpty(str: string): boolean { 202 | return str !== ""; 203 | } 204 | 205 | /** 206 | * Returns an Option of Some type of the last character of the string or None if the string is empty. 207 | * 208 | * @param str - String to check 209 | * 210 | * @example 211 | * ``` 212 | * const x = pipe("hello", S.last, O.unwrapOr("nope")); 213 | * const y = pipe("", S.last, O.unwrapOr("nope")); 214 | * 215 | * assertEquals(x, "o"); 216 | * assertEquals(y, "nope"); 217 | * ``` 218 | */ 219 | export function last(str: string): Option { 220 | return str === "" ? none : some(str[str.length - 1]); 221 | } 222 | 223 | /** 224 | * Returns the length of the string. 225 | * 226 | * @param str - String to check 227 | * 228 | * @example 229 | * ``` 230 | * const x = pipe("hello", S.length); 231 | * 232 | * assertEquals(x, 5); 233 | * ``` 234 | */ 235 | export function length(str: string): number { 236 | return str.length; 237 | } 238 | 239 | /** 240 | * Returns an array of strings split by line breaks. An empty array is returned if the string is empty. 241 | * 242 | * @param str - String to split 243 | * 244 | * @example 245 | * ``` 246 | * const x = pipe("hello\nworld\r\n!", S.lines); 247 | * 248 | * assertEquals(x, ["hello", "world", "!"]); 249 | * ``` 250 | */ 251 | export function lines(str: string): string[] { 252 | return str === "" ? [] : str.split(/\r\n|\n|\r/); 253 | } 254 | 255 | /** 256 | * Returns a new string with the prefix prepended to the beginning of the string. 257 | * 258 | * @param str - String to prepend to 259 | * @param prefix - String to prepend 260 | * 261 | * @example 262 | * ``` 263 | * const x = pipe("hello", S.prepend("world")); 264 | * 265 | * assertEquals(x, "worldhello"); 266 | * ``` 267 | */ 268 | export function prepend(str: string, prefix: string): string; 269 | export function prepend(prefix: string): (str: string) => string; 270 | export function prepend( 271 | strOrPrefix: string, 272 | prefix?: string 273 | ): string | ((str: string) => string) { 274 | if (arguments.length === 1) { 275 | return (_str: string) => prepend(_str, strOrPrefix); 276 | } 277 | 278 | return prefix + strOrPrefix; 279 | } 280 | 281 | /** 282 | * Returns a new string with the first occurence of the substring removed from the string. 283 | * 284 | * @param str - String to remove the substring from 285 | * @param substr - String to remove 286 | * 287 | * @example 288 | * ``` 289 | * const x = pipe("hello", S.remove("ell")); 290 | * const y = pipe("hello hi hello", S.remove("hello")); 291 | * 292 | * assertEquals(x, "ho"); 293 | * assertEquals(y, " hi hello"); 294 | * ``` 295 | */ 296 | export function remove(str: string, substr: string): string; 297 | export function remove(substr: string): (str: string) => string; 298 | export function remove( 299 | strOrSuffix: string, 300 | substr?: string 301 | ): string | ((str: string) => string) { 302 | if (arguments.length === 1) { 303 | return (_str: string) => remove(_str, strOrSuffix); 304 | } 305 | 306 | return strOrSuffix.replace(substr!, ""); 307 | } 308 | 309 | /** 310 | * removeAll 311 | * Returns a new string with all occurences of the substring removed from the string. 312 | * 313 | * @param str - String to remove the substring from 314 | * @param substr - String to remove 315 | * 316 | * @example 317 | * ``` 318 | * const x = pipe("hello", S.removeAll("ell")); 319 | * const y = pipe("hello hi hello", S.removeAll("hello")); 320 | * 321 | * assertEquals(x, "ho"); 322 | * assertEquals(y, " hi "); 323 | * ``` 324 | */ 325 | export function removeAll(str: string, substr: string): string; 326 | export function removeAll(substr: string): (str: string) => string; 327 | export function removeAll( 328 | strOrSuffix: string, 329 | substr?: string 330 | ): string | ((str: string) => string) { 331 | if (arguments.length === 1) { 332 | return (_str: string) => removeAll(_str, strOrSuffix); 333 | } 334 | 335 | return strOrSuffix.replaceAll(substr!, ""); 336 | } 337 | 338 | /** 339 | * Returns a new string with the first occurence of the substring replaced with the replacement string. 340 | * 341 | * @param str - String to replace the substring in 342 | * @param substr - String to replace 343 | * @param replacement - String to replace the substring with 344 | * 345 | * @example 346 | * ``` 347 | * const x = pipe("hello", S.replace("ell", "ipp")); 348 | * const y = pipe("hello hi hello", S.replace("hello", "bye")); 349 | * 350 | * assertEquals(x, "hippo"); 351 | * assertEquals(y, "bye hi hello"); 352 | * ``` 353 | */ 354 | export function replace( 355 | str: string, 356 | substr: string, 357 | replacement: string 358 | ): string; 359 | export function replace( 360 | substr: string, 361 | replacement: string 362 | ): (str: string) => string; 363 | export function replace( 364 | strOrSuffix: string, 365 | substrOrReplacement: string, 366 | replacement?: string 367 | ): string | ((str: string) => string) { 368 | if (arguments.length === 2) { 369 | return (_str: string) => replace(_str, strOrSuffix, substrOrReplacement); 370 | } 371 | 372 | return strOrSuffix.replace(substrOrReplacement, replacement!); 373 | } 374 | 375 | /** 376 | * Returns a new string with all occurences of the substring replaced with the replacement string. 377 | * 378 | * @param str - String to replace the substring in 379 | * @param substr - String to replace 380 | * 381 | * @example 382 | * ``` 383 | * const x = pipe("hello", S.replaceAll("ell", "ipp")); 384 | * const y = pipe("hello hi hello", S.replaceAll("hello", "bye")); 385 | * 386 | * assertEquals(x, "hippo"); 387 | * assertEquals(y, "bye hi bye"); 388 | * ``` 389 | */ 390 | export function replaceAll( 391 | str: string, 392 | substr: string, 393 | replacement: string 394 | ): string; 395 | export function replaceAll( 396 | substr: string, 397 | replacement: string 398 | ): (str: string) => string; 399 | export function replaceAll( 400 | strOrSuffix: string, 401 | substrOrReplacement: string, 402 | replacement?: string 403 | ): string | ((str: string) => string) { 404 | if (arguments.length === 2) { 405 | return (_str: string) => replaceAll(_str, strOrSuffix, substrOrReplacement); 406 | } 407 | 408 | return strOrSuffix.replaceAll(substrOrReplacement, replacement!); 409 | } 410 | 411 | /** 412 | * Returns a new string with it's characters reversed. 413 | * 414 | * @param str - String to reverse 415 | * 416 | * @example 417 | * ``` 418 | * const x = pipe("hello", S.reverse); 419 | * 420 | * assertEquals(x, "olleh"); 421 | * ``` 422 | */ 423 | export function reverse(str: string): string { 424 | return str.split("").reverse().join(""); 425 | } 426 | 427 | /** 428 | * Returns an array of strings split at every occurence of the delimiter. 429 | * 430 | * @param str - String to split 431 | * @param delimiter - Delimiter to split the string at 432 | * 433 | * @example 434 | * ``` 435 | * const x = pipe("hello world", S.split(" ")); 436 | * const y = pipe("hello", S.split("")); 437 | * 438 | * assertEquals(x, ["hello", "world"]); 439 | * assertEquals(y, ["h", "e", "l", "l", "o"]); 440 | * ``` 441 | */ 442 | export function split(str: string, delimiter: string): string[]; 443 | export function split(delimiter: string): (str: string) => string[]; 444 | export function split( 445 | strOrDelimiter: string, 446 | delimiter?: string 447 | ): string[] | ((str: string) => string[]) { 448 | if (arguments.length === 1) { 449 | return (_str: string) => split(_str, strOrDelimiter); 450 | } 451 | 452 | return strOrDelimiter.split(delimiter!); 453 | } 454 | 455 | /** 456 | * splitAt 457 | * Returns an array of two strings split at the index, the first string being the characters before the index and the second string being the characters at and after the index. 458 | * 459 | * @param str - String to split 460 | * @param index - Index to split the string at 461 | * 462 | * @example 463 | * ``` 464 | * const x = pipe("hello", S.splitAt(2)); 465 | * const y = pipe("hello", S.splitAt(0)); 466 | * 467 | * assertEquals(x, ["he", "llo"]); 468 | * assertEquals(y, ["", "hello"]); 469 | * ``` 470 | */ 471 | export function splitAt(str: string, index: number): [string, string]; 472 | export function splitAt(index: number): (str: string) => [string, string]; 473 | export function splitAt( 474 | strOrIndex: string | number, 475 | index?: number 476 | ): [string, string] | ((str: string) => [string, string]) { 477 | if (arguments.length === 1) { 478 | return (_str: string) => splitAt(_str, strOrIndex as number); 479 | } 480 | 481 | return [ 482 | (strOrIndex as string).slice(0, index!), 483 | (strOrIndex as string).slice(index!), 484 | ]; 485 | } 486 | 487 | /** 488 | * Returns true if the string starts with the prefix, false otherwise. 489 | * 490 | * @param str - String to check 491 | * @param prefix - Prefix to check the string starts with 492 | * 493 | * @example 494 | * ``` 495 | * const x = pipe("hello", S.startsWith("he")); 496 | * const y = pipe("hello", S.startsWith("lo")); 497 | * 498 | * assertEquals(x, true); 499 | * assertEquals(y, false); 500 | * ``` 501 | */ 502 | export function startsWith(str: string, prefix: string): boolean; 503 | export function startsWith(prefix: string): (str: string) => boolean; 504 | export function startsWith( 505 | strOrPrefix: string, 506 | prefix?: string 507 | ): boolean | ((str: string) => boolean) { 508 | if (arguments.length === 1) { 509 | return (_str: string) => startsWith(_str, strOrPrefix); 510 | } 511 | 512 | return strOrPrefix.startsWith(prefix!); 513 | } 514 | 515 | /** 516 | * Returns an array of the characters in the string. 517 | * 518 | * @param str - String to convert to an array 519 | * 520 | * @example 521 | * ``` 522 | * const x = pipe("hello", S.toArray); 523 | * 524 | * assertEquals(x, ["h", "e", "l", "l", "o"]); 525 | * ``` 526 | */ 527 | export function toArray(str: string): string[] { 528 | return str.split(""); 529 | } 530 | 531 | /** 532 | * Returns a new string with all characters converted to lowercase. 533 | * 534 | * @param str - String to convert to lowercase 535 | * 536 | * @example 537 | * ``` 538 | * const x = pipe("Hello", S.toLower); 539 | * 540 | * assertEquals(x, "hello"); 541 | * ``` 542 | */ 543 | export function toLower(str: string): string { 544 | return str.toLowerCase(); 545 | } 546 | 547 | /** 548 | * Returns a new string with all characters converted to lowercase. 549 | * 550 | * @param str - String to convert to lowercase 551 | * 552 | * @example 553 | * ``` 554 | * const x = pipe("Hello", S.toUpper); 555 | * 556 | * assertEquals(x, "HELLO"); 557 | * ``` 558 | */ 559 | export function toUpper(str: string): string { 560 | return str.toUpperCase(); 561 | } 562 | 563 | /** 564 | * Returns a new string with whitespaces trimmed from the start and end of the string. 565 | * 566 | * @param str - String to trim 567 | * 568 | * @example 569 | * ``` 570 | * const x = pipe(" hello ", S.trim); 571 | * 572 | * assertEquals(x, "hello"); 573 | * ``` 574 | */ 575 | export function trim(str: string): string { 576 | return str.trim(); 577 | } 578 | 579 | /** 580 | * Returns a new string with whitespaces trimmed from the end of the string. 581 | * 582 | * @param str - String to trim 583 | * 584 | * @example 585 | * ``` 586 | * const x = pipe(" hello ", S.trimEnd); 587 | * 588 | * assertEquals(x, " hello"); 589 | * ``` 590 | */ 591 | export function trimEnd(str: string): string { 592 | return str.trimEnd(); 593 | } 594 | 595 | /** 596 | * Returns a new string with whitespaces trimmed from the beginning of the string. 597 | * 598 | * @param str - String to trim 599 | * 600 | * @example 601 | * ``` 602 | * const x = pipe(" hello ", S.trimStart); 603 | * 604 | * assertEquals(x, "hello "); 605 | * ``` 606 | */ 607 | export function trimStart(str: string): string { 608 | return str.trimStart(); 609 | } 610 | 611 | /** 612 | * Returns an array of words in the string by splitting at whitespaces. If the string is empty, an empty array is returned. 613 | * 614 | * @param str - String to split into words 615 | * 616 | * @example 617 | * ``` 618 | * const x = pipe("hello world", S.words); 619 | * const y = pipe("", S.words); 620 | * 621 | * assertEquals(x, ["hello", "world"]); 622 | * assertEquals(y, []); 623 | * ``` 624 | */ 625 | export function words(str: string): string[] { 626 | return str === "" ? [] : str.split(" "); 627 | } 628 | -------------------------------------------------------------------------------- /String_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts"; 2 | 3 | import * as S from "./String.ts"; 4 | import * as O from "./Option.ts"; 5 | import * as A from "./Array.ts"; 6 | import { pipe } from "./Function.ts"; 7 | 8 | Deno.test("String - append", () => { 9 | const x = "Hello"; 10 | const y = "World"; 11 | 12 | const z = pipe(x, S.append(" "), S.append(y), S.append("!")); 13 | 14 | assertEquals(S.append(x, y), "HelloWorld"); 15 | assertEquals(z, "Hello World!"); 16 | }); 17 | 18 | Deno.test("String - endsWith", () => { 19 | const x = pipe("Hello World!", S.endsWith("!")); 20 | const y = pipe("Hello World!", S.endsWith("!!")); 21 | 22 | assertEquals(x, true); 23 | assertEquals(y, false); 24 | }); 25 | 26 | Deno.test("String - charAt", () => { 27 | const x = pipe("Hello World!", S.charAt(0), O.unwrapOr("Not Found")); 28 | const y = pipe("Hello World!", S.charAt(12), O.unwrapOr("Not Found")); 29 | 30 | assertEquals(x, "H"); 31 | assertEquals(y, "Not Found"); 32 | }); 33 | 34 | Deno.test("String - head", () => { 35 | const x = pipe("Hello World!", S.head, O.unwrapOr("Not Found")); 36 | const y = pipe("", S.head, O.unwrapOr("Not Found")); 37 | 38 | assertEquals(x, "H"); 39 | assertEquals(y, "Not Found"); 40 | }); 41 | 42 | Deno.test("String - inclues", () => { 43 | const x = pipe("Hello World!", S.includes("Hello")); 44 | const y = pipe("Hello World!", S.includes("Goodbye")); 45 | 46 | assertEquals(x, true); 47 | assertEquals(y, false); 48 | }); 49 | 50 | Deno.test("String - isEmpty", () => { 51 | const x = pipe("Hello World!", S.isEmpty); 52 | const y = pipe("", S.isEmpty); 53 | 54 | assertEquals(x, false); 55 | assertEquals(y, true); 56 | }); 57 | 58 | Deno.test("String - isNonEmpty", () => { 59 | const x = pipe("Hello World!", S.isNonEmpty); 60 | const y = pipe("", S.isNonEmpty); 61 | 62 | assertEquals(x, true); 63 | assertEquals(y, false); 64 | }); 65 | 66 | Deno.test("String - last", () => { 67 | const x = pipe("Hello World!", S.last, O.unwrapOr("Not Found")); 68 | const y = pipe("", S.last, O.unwrapOr("Not Found")); 69 | 70 | assertEquals(x, "!"); 71 | assertEquals(y, "Not Found"); 72 | }); 73 | 74 | Deno.test("String - length", () => { 75 | const x = pipe("Hello World!", S.length); 76 | 77 | assertEquals(x, 12); 78 | }); 79 | 80 | Deno.test("String - prepend", () => { 81 | const x = "Hello"; 82 | const y = "World"; 83 | 84 | const z = pipe(x, S.prepend(" "), S.prepend(y), S.prepend("!")); 85 | 86 | assertEquals(S.prepend(x, y), "WorldHello"); 87 | assertEquals(z, "!World Hello"); 88 | }); 89 | 90 | Deno.test("String - remove", () => { 91 | const x = pipe("Hello World!", S.remove("Hello")); 92 | const y = pipe("Hello World!", S.remove("Goodbye")); 93 | const z = pipe("Hello Bonjour Hello", S.remove("Hello")); 94 | 95 | assertEquals(x, " World!"); 96 | assertEquals(y, "Hello World!"); 97 | assertEquals(z, " Bonjour Hello"); 98 | }); 99 | 100 | Deno.test("String - removeAll", () => { 101 | const x = pipe("Hello World!", S.removeAll("Hello")); 102 | const y = pipe("Hello World!", S.removeAll("Goodbye")); 103 | const z = pipe("Hello Bonjour Hello", S.removeAll("Hello")); 104 | 105 | assertEquals(x, " World!"); 106 | assertEquals(y, "Hello World!"); 107 | assertEquals(z, " Bonjour "); 108 | }); 109 | 110 | Deno.test("String - replace", () => { 111 | const x = pipe("Hello World!", S.replace("Hello", "Goodbye")); 112 | const y = pipe("Hello World!", S.replace("Goodbye", "Hello")); 113 | 114 | assertEquals(x, "Goodbye World!"); 115 | assertEquals(y, "Hello World!"); 116 | }); 117 | 118 | Deno.test("String - replaceAll", () => { 119 | const x = pipe("Hello World!", S.replaceAll("Hello", "Goodbye")); 120 | const y = pipe("Hello World!", S.replaceAll("Goodbye", "Hello")); 121 | const z = pipe("Hello Bonjour Hello", S.replaceAll("Hello", "Goodbye")); 122 | 123 | assertEquals(x, "Goodbye World!"); 124 | assertEquals(y, "Hello World!"); 125 | assertEquals(z, "Goodbye Bonjour Goodbye"); 126 | }); 127 | 128 | Deno.test("String - reverse", () => { 129 | const x = pipe("Hello World!", S.reverse); 130 | 131 | assertEquals(x, "!dlroW olleH"); 132 | }); 133 | 134 | Deno.test("String - split", () => { 135 | const x = pipe("Hello World!", S.split(" ")); 136 | const y = pipe("Hello World!", S.split("o")); 137 | 138 | assertEquals(x, ["Hello", "World!"]); 139 | assertEquals(y, ["Hell", " W", "rld!"]); 140 | }); 141 | 142 | Deno.test("String - splitAt", () => { 143 | const x = pipe("Hello World!", S.splitAt(5)); 144 | const y = pipe("Hello World!", S.splitAt(12)); 145 | const z = pipe("Hello World!", S.splitAt(0)); 146 | 147 | assertEquals(x, ["Hello", " World!"]); 148 | assertEquals(y, ["Hello World!", ""]); 149 | assertEquals(z, ["", "Hello World!"]); 150 | }); 151 | 152 | Deno.test("String - startsWith", () => { 153 | const x = pipe("Hello World!", S.startsWith("Hello")); 154 | const y = pipe("Hello World!", S.startsWith("Goodbye")); 155 | 156 | assertEquals(x, true); 157 | assertEquals(y, false); 158 | }); 159 | 160 | Deno.test("String - trim, trimEnd, trimStart", () => { 161 | const str = " Hello World! "; 162 | const x = pipe(str, S.trim); 163 | const y = pipe(str, S.trimEnd); 164 | const z = pipe(str, S.trimStart); 165 | 166 | assertEquals(str, " Hello World! "); 167 | assertEquals(x, "Hello World!"); 168 | assertEquals(y, " Hello World!"); 169 | assertEquals(z, "Hello World! "); 170 | }); 171 | 172 | Deno.test("String - toArray", () => { 173 | const x = pipe("Hello World!", S.toArray, A.uniq); 174 | const y = pipe("", S.toArray); 175 | 176 | assertEquals(x, ["H", "e", "l", "o", " ", "W", "r", "d", "!"]); 177 | assertEquals(y, []); 178 | }); 179 | 180 | Deno.test("String - toLower, toUpper", () => { 181 | const str = "Hello World!"; 182 | 183 | const x = pipe(str, S.toLower); 184 | const y = pipe(str, S.toUpper); 185 | 186 | assertEquals(str, "Hello World!"); 187 | assertEquals(x, "hello world!"); 188 | assertEquals(y, "HELLO WORLD!"); 189 | }); 190 | 191 | Deno.test("String - words", () => { 192 | const str = "Hello World!"; 193 | 194 | const x = pipe(str, S.words); 195 | const y = pipe("", S.words); 196 | 197 | assertEquals(str, "Hello World!"); 198 | assertEquals(x, ["Hello", "World!"]); 199 | assertEquals(y, []); 200 | }); 201 | 202 | Deno.test("String - lines", () => { 203 | const str = "Hello\nWorld!\nnice hahahha\r\nlollllllllll"; 204 | 205 | const x = pipe(str, S.lines); 206 | const y = pipe("", S.lines); 207 | 208 | assertEquals(str, "Hello\nWorld!\nnice hahahha\r\nlollllllllll"); 209 | assertEquals(x, ["Hello", "World!", "nice hahahha", "lollllllllll"]); 210 | assertEquals(y, []); 211 | }); 212 | 213 | Deno.test("String - capitalize", () => { 214 | const x = pipe("hello world", S.capitalize); 215 | 216 | assertEquals(x, "Hello world"); 217 | }); 218 | 219 | Deno.test("String - capitalizeAll", () => { 220 | const x = pipe("hello world", S.capitalizeAll); 221 | 222 | assertEquals(x, "Hello World"); 223 | }); 224 | -------------------------------------------------------------------------------- /Typeguards.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file ban-types 2 | export const isFunction = (value: unknown): value is Function => 3 | typeof value === "function"; 4 | 5 | export const isString = (value: unknown): value is string => 6 | typeof value === "string"; 7 | 8 | export const isObject = (value: unknown): value is object => 9 | typeof value === "object" && value !== null; 10 | 11 | export const isNumber = (value: unknown): value is number => 12 | typeof value === "number"; 13 | 14 | export const isBoolean = (value: unknown): value is boolean => 15 | typeof value === "boolean"; 16 | 17 | export const isNull = (value: unknown): value is null => value === null; 18 | 19 | export const isUndefined = (value: unknown): value is undefined => 20 | value === undefined; 21 | 22 | export const isSymbol = (value: unknown): value is symbol => 23 | typeof value === "symbol"; 24 | 25 | export const isBigInt = (value: unknown): value is bigint => 26 | typeof value === "bigint"; 27 | 28 | export const isDate = (value: unknown): value is Date => value instanceof Date; 29 | 30 | export const isRegExp = (value: unknown): value is RegExp => 31 | value instanceof RegExp; 32 | 33 | export const isPromise = (value: unknown): value is Promise => 34 | isObject(value) && isFunction((value as Promise).then); 35 | 36 | export const isGenerator = (value: unknown): value is Generator => 37 | isObject(value) && isFunction((value as Generator).next); 38 | 39 | export const isGeneratorFunction = ( 40 | value: unknown 41 | ): value is GeneratorFunction => 42 | isFunction(value) && value.constructor.name === "GeneratorFunction"; 43 | 44 | export const isIterable = (value: unknown): value is Iterable => 45 | isObject(value) && isFunction((value as Iterable)[Symbol.iterator]); 46 | 47 | export const isAsyncIterable = ( 48 | value: unknown 49 | ): value is AsyncIterable => 50 | isObject(value) && 51 | isFunction((value as AsyncIterable)[Symbol.asyncIterator]); 52 | 53 | export const isArray = (value: unknown): value is T[] => 54 | Array.isArray(value); 55 | 56 | export const isSet = (value: unknown): value is Set => 57 | value instanceof Set; 58 | 59 | export const isMap = (value: unknown): value is Map => 60 | value instanceof Map; 61 | -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "dev": "deno run --watch main.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2", 3 | "remote": { 4 | "https://deno.land/std@0.177.0/fmt/colors.ts": "938c5d44d889fb82eff6c358bea8baa7e85950a16c9f6dae3ec3a7a729164471", 5 | "https://deno.land/std@0.177.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", 6 | "https://deno.land/std@0.177.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", 7 | "https://deno.land/std@0.177.0/testing/asserts.ts": "984ab0bfb3faeed92ffaa3a6b06536c66811185328c5dd146257c702c41b01ab" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /docs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 18 | 19 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 20 | 21 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 22 | 23 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 24 | 25 | ## Learn More 26 | 27 | To learn more about Next.js, take a look at the following resources: 28 | 29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 31 | 32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 33 | 34 | ## Deploy on Vercel 35 | 36 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 37 | 38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 39 | -------------------------------------------------------------------------------- /docs/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | }; 5 | 6 | const withNextra = require("nextra")({ 7 | theme: "nextra-theme-docs", 8 | themeConfig: "./theme.config.jsx", 9 | defaultShowCopyCode: true 10 | }); 11 | 12 | module.exports = withNextra(); 13 | 14 | // If you have other Next.js configurations, you can pass them as the parameter: 15 | // module.exports = withNextra({ /* other next.js config */ }) 16 | 17 | module.exports = withNextra(nextConfig); 18 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@next/font": "13.1.6", 13 | "@types/node": "18.14.0", 14 | "@types/react": "18.0.28", 15 | "@types/react-dom": "18.0.11", 16 | "eslint": "8.34.0", 17 | "eslint-config-next": "13.1.6", 18 | "next": "13.1.6", 19 | "nextra": "^2.2.14", 20 | "nextra-theme-docs": "^2.2.14", 21 | "react": "18.2.0", 22 | "react-dom": "18.2.0", 23 | "typescript": "4.9.5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from "next/app"; 2 | 3 | export default function App({ Component, pageProps }: AppProps) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /docs/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /docs/pages/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": "Introduction", 3 | "function": "Function", 4 | "option": "Option", 5 | "array": "Array", 6 | "string": "String" 7 | } 8 | -------------------------------------------------------------------------------- /docs/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /docs/pages/array.mdx: -------------------------------------------------------------------------------- 1 | import { Callout } from "nextra-theme-docs"; 2 | 3 | # Array 4 | 5 | This module contains functions to work with arrays. 6 | 7 | ## Usage 8 | 9 | ### Import 10 | 11 | ```ts 12 | import * as A from "https://deno.land/x/fp_/Array.ts"; 13 | ``` 14 | 15 | ### Functions 16 | 17 | #### all 18 | 19 | `all` takes a predicate function and an array and returns true if all elements of the array satisfy the predicate. 20 | 21 | 22 | `all` works exactly like 23 | [`Array.prototype.every`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) 24 | 25 | 26 | ```ts 27 | const isEven = (n: number) => n % 2 === 0; 28 | 29 | const x = pipe([2, 4, 6], A.all(isEven)); 30 | 31 | assertEquals(x, true); 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/pages/function.mdx: -------------------------------------------------------------------------------- 1 | # Function 2 | 3 | This module contains common function composition utilities. 4 | 5 | ## pipe 6 | 7 | The `pipe` function is used to chain function calls together in a more readable way. It takes a list of functions and passes the result of the first function to the second, second function to the third, and so on. The result of the last function is returned. 8 | 9 | ### Import 10 | 11 | ```ts 12 | import { pipe } from "https://deno.land/x/fp_/Function.ts"; 13 | ``` 14 | 15 | ### Usage 16 | 17 | #### Basic Example 18 | 19 | ```ts 20 | const length = (s: string): number => s.length; 21 | const double = (a: number): number => a * 2; 22 | 23 | const result = pipe( 24 | "hi", // initial value 25 | length, // "hi" is passed to `length` which returns 2 26 | double // 2 is passed to `double` which returns 4 27 | ); 28 | 29 | assertEquals(result, 4); 30 | ``` 31 | 32 | Here the `pipe` function is used to chain together the `length` and `double` functions. The `length` function is passed the initial value `"hi"` and returns `2`. The `double` function is passed `2` and returns `4`. 33 | 34 | #### Functions With Multiple Arguments (Currying) 35 | 36 | In the previous example, all of the functions just take one argument. What if we want to pass multiple arguments to a function? The `pipe` function can only pass one argument to a function, which is the output of the previous function. We can work around this by defining functions that take a single argument and return a function that takes the next argument. This is called [currying](https://en.wikipedia.org/wiki/Currying) which is a core concept in functional programming. 37 | 38 | Let's look at how we can implement a function to add two numbers together using currying. 39 | 40 | ```ts 41 | // normal function 42 | function normalAdd(a: number, b: number) { 43 | return a + b; 44 | } 45 | 46 | // curried function 47 | function curriedAdd(a: number) { 48 | return (b: number) => a + b; 49 | } 50 | ``` 51 | 52 | The `curriedAdd` function takes one argument and returns a function that takes the next argument. The returned function then adds the two arguments together and returns the result. This can be made more concise by using an arrow function. 53 | 54 | ```ts 55 | const add = (a: number) => (b: number) => a + b; 56 | ``` 57 | 58 | Now we can use the `add` function in our pipeline. 59 | 60 | ```ts 61 | const length = (s: string): number => s.length; 62 | const double = (a: number): number => a * 2; 63 | const add = (a: number) => (b: number) => a + b; 64 | 65 | const result = pipe( 66 | "hi", // initial value 67 | length, // "hi" is passed to `length` which returns 2 68 | double // 2 is passed to `double` which returns 4 69 | add(5) // 4 is passed to add which then returns a function that takes the next argument (5) and returns 9 70 | ); 71 | 72 | assertEquals(result, 9); 73 | ``` 74 | 75 | ## compose 76 | 77 | The `compose` function is similar to the `pipe` function, but instead of returning the result of the pipeline, it chains all the functions passed to it into a single function. 78 | 79 | ### Import 80 | 81 | ```ts 82 | import { compose } from "https://deno.land/x/fp_/Function.ts"; 83 | ``` 84 | 85 | ### Usage 86 | 87 | #### Basic Example 88 | 89 | ```ts 90 | const length = (s: string): number => s.length; 91 | const double = (a: number): number => a * 2; 92 | 93 | const lengthAndDouble = compose(length, double); 94 | 95 | const result = lengthAndDouble("hi"); 96 | 97 | assertEquals(result, 4); 98 | ``` 99 | 100 | Here the `compose` function is used to chain together the `length` and `double` functions. The flow of the functions is the same as the `pipe` function. The `length` function is passed the initial value `"hi"` and returns `2`. The `double` function is passed `2` and returns `4`. 101 | 102 | #### Functions With Multiple Arguments (Currying) 103 | 104 | `compose` can be used with curried functions in the same way as `pipe`. 105 | 106 | ```ts 107 | const length = (s: string): number => s.length; 108 | const double = (a: number): number => a * 2; 109 | const add = (a: number) => (b: number) => a + b; 110 | 111 | const lengthAndDoubleAndAdd = compose(length, double, add(5)); 112 | 113 | const result = lengthAndDoubleAndAdd("hi"); 114 | 115 | assertEquals(result, 9); 116 | ``` 117 | 118 | ## pipe vs compose 119 | 120 | The `pipe` and `compose` functions are almost analogous to each other. The difference is that `pipe` takes any arbitrary value as it's first argument and passes it to a chain of functions and returns the result. `compose` takes a function as it's first argument and chains it with a chain of functions and returns a new function that can be called with any arbitrary value as long it matches the type of the first function. 121 | 122 | Use `pipe` when you just want the result of a value through a chain of functions. Always default to `pipe` unless you have a specific reason to use `compose`. 123 | 124 | So what are the reasons to use `compose`? Let's say we have a function called `sumTransform` that takes a list of numbers and returns the sum of the numbers after they have been transformed by a function. 125 | 126 | ```ts 127 | const sumTransform = (numbers: number[], transform: (n: number) => number) => 128 | numbers.reduce((acc, n) => acc + transform(n), 0); 129 | ``` 130 | 131 | We can use `pipe` to pass the transform function to `sumTransform`. 132 | 133 | ```ts 134 | const sum = sumTransform([1, 2, 3], (n) => pipe(n, double, add(1))); 135 | 136 | assertEquals(sum, 15); 137 | ``` 138 | 139 | But the problem with this is that we have to pass an anonymous function to use `pipe` inside of `sumTransform`. We also need to declare an extra variable `n` to hold the current number. Every extra variable is an extra thing to keep track of and can lead to bugs. This might seem like a small thing, but it can add up quickly in a large codebase. 140 | 141 | This is where `compose` comes in. We can use `compose` to pass the transform function to `sumTransform`. 142 | 143 | ```ts 144 | const sum = sumTransform([1, 2, 3], compose(double, add(1))); 145 | 146 | assertEquals(sum, 15); 147 | ``` 148 | 149 | This is much cleaner and easier to read. 150 | 151 | ### tl;dr 152 | 153 | - Use `pipe` when you just want the result of a value through a chain of functions. 154 | - Use `compose` when you want to pass a function to another function. 155 | - Always default to `pipe` unless you have a specific reason to use `compose`. 156 | -------------------------------------------------------------------------------- /docs/pages/index.mdx: -------------------------------------------------------------------------------- 1 | # Welcome to fp_ 2 | 3 | ## Overview 4 | 5 | `fp_` is a functional programming library for Deno. It is mostly a personal project that I started because there wasn't a good functional programming library for Deno, and also I wanted to learn more about functional programming. I have ambitious goals for this project, and I hope to make it the most complete and well documented functional programming library for Deno. 6 | 7 | ## Who is this for? 8 | 9 | This is for people experienced or beginning to dabble into the world of functional programming. `fp_` will always be well documented, and I will try to make it as easy to use as possible. 10 | 11 | ## Status 12 | 13 | `fp_` is currently in development, and is not ready for production use. I will be adding more features and documentation as I go along. 14 | -------------------------------------------------------------------------------- /docs/pages/option.mdx: -------------------------------------------------------------------------------- 1 | import { Callout } from "nextra-theme-docs"; 2 | 3 | # Option 4 | 5 | This module contains an implementation of the `Option` type and it's associated functions. 6 | 7 | ## What is the Option Type? 8 | 9 | The `Option` type allows you to represent the possibility of a value being present or absent. It is a way to deal with null values in a functional way. There are two types of `Option` values: `Some` and `None`. `Some` represents a value that is present, and `None` represents a value that is absent. 10 | 11 | `Option` is the same as the enum of the same name in [Rust](https://doc.rust-lang.org/std/option/enum.Option.html) or the Maybe monad in [Haskell](https://wiki.haskell.org/Maybe) where the `Some` type is called `Just` and the `None` type is called `Nothing`. 12 | 13 | ## Why use Option? 14 | 15 | The Option type makes your code declarative and less verbose. It also makes your code more robust and less error-prone by forcing you to deal with the possibility of a value being absent. 16 | 17 | ## Usage 18 | 19 | ### Import 20 | 21 | ```ts 22 | import * as O from "https://deno.land/x/fp_/Option.ts"; 23 | ``` 24 | 25 | ### Primitives 26 | 27 | #### Some 28 | 29 | `Some` is a type that represents a value that is present. 30 | 31 | ```ts 32 | type Some = { 33 | readonly type: "some"; 34 | readonly value: T; 35 | }; 36 | ``` 37 | 38 | #### None 39 | 40 | ```ts 41 | type None = { 42 | readonly type: "none"; 43 | }; 44 | ``` 45 | 46 | #### Option 47 | 48 | `Option` is a union of the `Some` and `None` types. 49 | 50 | ```ts 51 | type Option = Some | None; 52 | ``` 53 | 54 | You won't be using these types directly for the most part, but they are useful to know about. 55 | 56 | #### some 57 | 58 | The `some` constructor can be used to create an `Option` value of type `Some`. 59 | 60 | ```ts 61 | const someValue = O.some(1); 62 | // ^? const someValue: Option 63 | ``` 64 | 65 | 66 | Only use the `some` constructor when you are sure that the value is present. 67 | 68 | 69 | #### none 70 | 71 | `none` is constant of type `Option`. 72 | 73 | ```ts 74 | const noneValue = O.none; 75 | // ^? const noneValue: Option 76 | ``` 77 | 78 | ### Creating an Option 79 | 80 | 81 | The following examples use the `pipe` function from the `Function` module. If 82 | you are not familiar with it, you can read more about it{" "} 83 | [here](/function#pipe). 84 | 85 | 86 | 87 | All the examples in this section have been kept as simple as possible so that 88 | they are easy to understand. In real-world programs you won't be writing code 89 | like this. 90 | 91 | 92 | #### fromNullable 93 | 94 | `fromNullable` creates an `Option` value and depending on the value passed in, it will either be `Some` or `None`. If the value is `null` or `undefined`, it will be `None`, otherwise it will be `Some`. 95 | 96 | ```ts 97 | const x = O.fromNullable(1); 98 | const y = O.fromNullable(null); 99 | const z = O.fromNullable(""); 100 | 101 | assertEquals(O.isSome(x), true); 102 | assertEquals(O.isNone(y), true); 103 | assertEquals(O.isSome(z), true); 104 | ``` 105 | 106 | #### fromFalsy 107 | 108 | `fromFalsy` creates an `Option` value and depending on the value passed in, it will either be `Some` or `None`. If the value is [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), it will be `None`, otherwise it will be `Some`. 109 | 110 | ```ts 111 | const x = O.fromFalsy(1); 112 | const y = O.fromFalsy(null); 113 | const z = O.fromFalsy(""); 114 | 115 | assertEquals(O.isSome(x), true); 116 | assertEquals(O.isNone(y), true); 117 | assertEquals(O.isNone(z), true); 118 | ``` 119 | 120 | #### fromException 121 | 122 | `fromException` takes a function that may throw an error and returns an `Option` value. If the function throws an error, the `Option` value will be `None`, otherwise it will be `Some`. 123 | 124 | ```ts 125 | const throwIfNotOne = (x: number): number => { 126 | if (x !== 1) { 127 | throw new Error("x is not 1"); 128 | } 129 | 130 | return x; 131 | }; 132 | 133 | const x = O.fromException(() => throwIfNotOne(1)); 134 | const y = O.fromException(() => throwIfNotOne(2)); 135 | 136 | assertEquals(O.isSome(x), true); 137 | assertEquals(O.isNone(y), true); 138 | ``` 139 | 140 | 141 | `fromException` is a direct replacement for the `try`/`catch` statement. 142 | 143 | 144 | ### Checking an Option 145 | 146 | #### isOption 147 | 148 | `isOption` checks if an unknown value is an `Option`. 149 | 150 | ```ts 151 | const x = O.fromNullable(1); 152 | const y = 1; 153 | 154 | assertEquals(O.isOption(x), true); 155 | assertEquals(O.isOption(y), false); 156 | ``` 157 | 158 | #### isSome 159 | 160 | `isSome` checks if an `Option` value is of type `Some`. 161 | 162 | ```ts 163 | const x = O.fromNullable(1); 164 | const y = O.fromNullable(null); 165 | 166 | assertEquals(O.isSome(x), true); 167 | assertEquals(O.isSome(y), false); 168 | ``` 169 | 170 | #### isNone 171 | 172 | `isNone` checks if an `Option` value is of type `None`. 173 | 174 | ```ts 175 | const x = O.fromNullable(1); 176 | const y = O.fromNullable(null); 177 | 178 | assertEquals(O.isNone(x), false); 179 | assertEquals(O.isNone(y), true); 180 | ``` 181 | 182 | ### Getting the value of an Option 183 | 184 | #### unwrap 185 | 186 | `unwrap` yields the content of a `Some` value. If the value is `None`, it will throw an error. 187 | 188 | ```ts 189 | const x = pipe(O.fromNullable(1), O.unwrap()); // x = 1 190 | 191 | const y = pipe(O.none, O.unwrap()); // throws an error 192 | ``` 193 | 194 | #### expect 195 | 196 | `expect` yields the content of a `Some` value. If the value is `None`, it will throw an error with the message provided as an argument. It's the same as `unwrap` except that it allows you to provide a custom error message. 197 | 198 | ```ts 199 | const x = pipe(O.some(1), O.expect("This should not happen")); 200 | // x = 1 201 | 202 | const y = pipe(O.none, O.expect("This should happen")); 203 | // throws an error with the message "This should happen" 204 | ``` 205 | 206 | #### unwrapOr 207 | 208 | `unwrapOr` yields the content of a `Some` value. If the value is `None`, it will return the value provided as an argument. It doesn't throw an error. 209 | 210 | ```ts 211 | const x = pipe(O.fromNullable(1), O.unwrapOr(0)); // unwraps some(1) to 1 212 | const y = pipe(O.fromNullable(null), O.unwrapOr(0)); // unwraps none to default value 0 213 | 214 | assertEquals(x, 1); 215 | assertEquals(y, 0); 216 | ``` 217 | 218 | #### unwrapOrElse 219 | 220 | `unwrapOrElse` yields the content of a `Some` value. If the value is `None`, it will return the value provided as a function. It doesn't throw an error. It's similar to `unwrapOr` except that it allows you to provide a function that returns the default value so that the default value is evaluated only when needed. This is called lazy evaluation. 221 | 222 | ```ts 223 | const x = pipe( 224 | O.fromNullable(1), 225 | O.unwrapOrElse(() => 0) 226 | ); // unwraps some(1) to 1 227 | 228 | const y = pipe( 229 | O.fromNullable(null), 230 | O.unwrapOrElse(() => 0) 231 | ); // unwraps none to lazily evaluated default value 0 232 | ``` 233 | 234 | ### Operations on an Option 235 | 236 | #### map 237 | 238 | `map` is used to apply a function to the content of a `Some` value. If the value is `None` it leaves it untouched. 239 | 240 | ```ts 241 | const x = pipe( 242 | O.fromNullable(1), 243 | O.map((n) => n + 1), 244 | O.unwrapOr(0) 245 | ); 246 | 247 | const y = pipe( 248 | O.fromNullable(null), 249 | O.map((n) => n + 1), 250 | O.unwrapOr(0) 251 | ); 252 | 253 | assertEquals(x, 2); 254 | assertEquals(y, 0); 255 | ``` 256 | 257 | #### flatMap 258 | 259 | The `flatMap` function is similar to the `map` function, but it allows you to return an `Option` from the function that you pass in. It's useful when you want to compose multiple `Option` values. 260 | 261 | For example, if you have a function that returns an `Option` value and you want to call another function that also returns an `Option` value, you can use `flatMap` to compose the two functions. 262 | 263 | ```ts 264 | const withMap = pipe( 265 | O.fromNullable(1), 266 | O.map((n) => O.fromNullable(n + 1)) 267 | ); // O.Option> 268 | 269 | const withFlatMap = pipe( 270 | O.fromNullable(1), 271 | O.flatMap((n) => O.fromNullable(n + 1)) 272 | ); // O.Option 273 | ``` 274 | 275 | #### match 276 | 277 | `match` allows for pattern matching on an `Option`. It takes two functions as arguments, the first one is used if the value is `Some` and the second one is used if the value is `None`. 278 | 279 | ```ts 280 | const x = pipe( 281 | O.fromNullable(1), 282 | O.match( 283 | (n) => n + 1, 284 | () => 0 285 | ) 286 | ); 287 | 288 | const y = pipe( 289 | O.fromNullable(null), 290 | O.match( 291 | (n) => n + 1, 292 | () => 0 293 | ) 294 | ); 295 | 296 | assertEquals(x, 2); 297 | assertEquals(y, 0); 298 | ``` 299 | 300 | #### tap 301 | 302 | `tap` allows you to perform a side effect on a `Some` value. It leaves a `None` value untouched. 303 | 304 | ```ts 305 | const x = pipe( 306 | O.fromNullable(1), 307 | O.tap((n) => console.log(n)) 308 | ); // logs 1 309 | 310 | const y = pipe( 311 | O.fromNullable(null), 312 | O.tap((n) => console.log(n)), 313 | O.unwrapOr(0) 314 | ); // logs nothing 315 | ``` 316 | 317 | 318 | Side effects are generally discouraged in functional programming. Use `tap` 319 | only if it is absolutely necessary. 320 | 321 | 322 | #### zip 323 | 324 | `zip` allows you to combine two `Option` values into a tuple. If both values are `Some`, it will return a `Some` value containing a tuple of the two values. If either value is `None`, it will return `None`. 325 | 326 | ```ts 327 | const x = pipe( 328 | O.fromNullable("hello"), 329 | O.zip(O.fromNullable("world")), 330 | O.unwrapOr(["", ""]) 331 | ); 332 | 333 | const y = pipe( 334 | O.fromNullable("hello"), 335 | O.zip(O.fromNullable(null)), 336 | O.unwrapOr(["", ""]) 337 | ); 338 | 339 | assertEquals(x, ["hello", "world"]); 340 | assertEquals(y, ["", ""]); 341 | ``` 342 | -------------------------------------------------------------------------------- /docs/pages/string.mdx: -------------------------------------------------------------------------------- 1 | coming soon 2 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- 1 |  (F  (n00 (-� ��F(  $]��]$ �������� 8����������8��������������������������#���OOO�������������������������ggg����#Y����������������������������555����Y�����kkk������������������������������� �����������������������Y�����JJJ���������kkk������Y#�������������� ������#������111�DDD�������������������8����������8 �������� $]��]$( @ ,U����U,*������������*����������������Q������������������Qr��������������������rr����������������������rO������������������������O������������������������������������������������������(����������������������������'�������888���������������������������������������������������������___������������������������������������������������������������������������SSS��������+��������hhh�������������������������������������������������������������+T���������������������������������������������������������,,,���������T����������GGG��������������������������������������������������������������������������������������������������������������������������������+++���������������������������������jjj��������������������������������������������������������������������T������������������������������������III������������T+������������hhh���������������������������������+�����������������������������,,,��������������������������GGG��������������������������'����������������������������������(�������������333�___����������������������������������������O������������������������Or����������������������rr��������������������rQ������������������Q����������������*������������*,U����U,(0` - (Lj����jK( V��������������U%��������������������&������������������������Q��������������������������R��������������������������������������������������������������������������������������������������������������������������������������������������������������������������P��������������������������������������O����������������������������������������������������������������������������������#������������������������������������������#������������������������������������������������������$$$�hhh�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�eee�PPP�����������U���������������������������������������������������������������������������������������������������sss�����������U������������eee������������������������������������������������������������������������������������������������� ���������������������������������������������������������������������������������������������HHH������������� (�������������EEE������������������������������������������������������������������������������������������(K��������������������������������������������������������������������������������������,,,��������������Lj��������������)))�����������������������������������������������������������������������������������j�������������������������������������������������������������������������������������������������������������������������������������������������������������������������iii����������������������������������eee����������������������������������������������������������������������������������������������������������������������������������������HHH������������������j�����������������EEE��������������������������������������������������������������jL����������������������������������������������������������,,,������������������K(������������������)))�������������������������������������������������������( ���������������������������������������������������������������������� ��������������������������������������������iii��������������������U�������������������eee����������������������������������������U������������������������������������HHH����������������������������������������EEE���������������������������������#����������������������������,,,��������������������#��������������������222�}}}�������������������������������������������������������������O��������������������������������������P��������������������������������������������������������������������������������������������������������������������������������������������������������������������������R��������������������������Q������������������������&��������������������%U��������������V (Kj����jL( �PNG 2 |  3 | IHDR\r�fsRGB���8eXIfMM*�i��D"8sIDATx�] �ՙn�]<QVA���h$ �N��13*�q��d�č�I���D�L2��(�(Ԙ2�ę�G ��q_@屈���xț�Џ��{o�������U�{}�O��;������9�‘d���(Dg��8 ��N�]��@ �hx�?v �N�3�=`;�6�.�&� �u�� ��6� P��н��@�àR�P�iZq�^DN���wp� � ��X�hИH g@�� 4 | :��|�5` p"@�'�ɲ�s{ �p�*�2����� dү���|(0� 5 | 0��>K� 6 | �xX�6IJ ��C|?$KE N�}ϓ|������h $ 2 � �|/�.Nz�# ���W�e� 7 | �5�� ����ܶ�� �;�y�� �g�s�h^I� �DL(�;�8�� Hjg�cH|x�1��R"�a���Ӂ�G��@��9`/`%0� H�@j �~,���K 8 | �,t).��I���D�T�O�)~��V�u$b 誛 �U%�7������_�$b 8A������J�3` 510wQ�?��vr���:�2�K�@ ��v*{%#��A�Z�咁^(��=�g\��W�����!:��,`�6��643�:@�c.Fٟ����u?�<��'������_܏vp: �8Q�� 9 | I�Ł� p{3���kHȢ�G�����c�Ѽ <�62&� 10 | ��2uC�����敭��T�3� 11 | �� ���;���d�/~m��.��X�@{�w.��d]G��{lK��Eb���(P�RuM�T�C���� �d��])��_Lm�=��=@b���K��GUk�^�U�������)1����g�T�Š��m`9�\����Q��@����Ⱆ6�:ڞ�^�w�����E�D ��� �5����F�,�� 12 | �X"�d�m�<�nB~�� @����t�t�x�� �;�f�>����I8����8��C1۪$B���e���+��jl��EZ��& ��S:�:�6�m����\G1��`���!�nl�l�Ɗ�^�Q`��@Oc�S��@e�ͷ���qb�p���S��@u p���F�D@�Г������2@#����L3�A��$H2�_h��FH #rq(��O�D�򤬈���runGOWa�b�&�SgD�3�ED�to�*Ǥ����9k��~)���,$�x�R�1�v�K ��9�D 䍁U(�w�&LE��ꩻ� S)��3�Y8x8$.i�(��K�ŀY ����a�]��� �4��ǀ c����@3�f����4�Ƣ�� �/*b������$!I�~� �7�B*-1` o �� �$��ǡD�����L�������J"���OQ��)��2@#�x4�"$e���I�8��Oi��8�"��G��8[x�t<�.��7&�m&؎R�^��tq�ؕ�.���Y�-2��d���*_��&d|j\�W�b � �G����*g����釁�F4�"I�؃�/b1q�N����Y�D ��p ���9���p�}w\��Ԥ���1 j`��O���xK=��H���A��1 �#� 13 | D:U8j���t���$b b�A||�U�Q��26%��)1 ��_ �ꢳ!~D�����+b >A��:]�E$��50��GDhR�t����ݻwR�)�� P���n$� 3���@bS�Nu�,Y�j�ʲ��:����;�����@�`�|�-[)�'OV��Ն�sFxڮ��ۥ�n}͛7�����~��ƺ�:���Q��J_��UKj8�q0x���;v4̞=[�hW=� �� �&�!e5�8hѢE��w�]�����6���_�iW}�SZ�? �/`�;vl�}��2<�h�"� ���A�܁�X,�m۶�+V�(��<�w���#F�^���;���aH�c� ��)S�*�{a���p ��c89(�^����4�&E��oÆ ��W�/��u�=�^���*?{k^�_E�����z���g��UI-���{WU* 14 | �:p�9 .tڷo(/ݺus>��3�'�^�Rg���ڞG��I_D���� ���~~���{ ���?N0�7�S��.ƍ׸�~?}/y]nA;�أ���2]�FOB2C?�_I����[�:�:�=#�OzK�-� ��ϣ�%����?j��I���P�ۯ��{N�-hU��t�:�������,���G�K�-hU���c�hP7 �� �˜�@�n?�\�-�k�.���2�:�� �`��F��=�-�V�_�G��܂V���}�0WI����F��ʭ���sM�r Z�8pJ�Q�*@OK8��� 15 | r Z� �ݖa,��w��S�W^y����.��5�at7��ݏ���Tv#�~7n ��A"�����+��W��pM��/�hK8����g��F/^������M{e��R�|�)q��7�t��?8'���K��P~���瞰�\��r ��>�ǷUk�eP��|�^x���� 16 | �/V/��v������ ���*�p�v������ʟ]J��}��k8(������ĉ�ѣGǗ�O�mڴq,X�o ���e. �^ �Qx���p�t����4^_�N�{�����y�2�s������-عsg�s���i�v��Z 8 17 | !~PJ?�c�������|�]�ܽ{��z�긓R��1pn���z�����tlp�9�f�r�v�jT殿�z�4*O�L�~����ԕ3��4�~~�r �;�m�xY�+��� ������3r �;�m�x�4���:7]Ձq L�4)U��!r �1��u�6���$� �7����8�w��̙3Ǹ|5�>?�\z��O� ��͆���,�E����3�����2���[����2Wu:E�����^p. H1cJ�t�]}��B�u��SOu�����I c�O�����%� �AZ������k����D?�5�@Q�� ���3�w�+��"��T��S��Uޥ�13��?� �5M'݋��>p��Z�j�~fj� ׈�סԐ�n�����>���i5D�[bf ��~a�'�`Xc���-�1�k����āI�������k��Q�ů|�k�M��(92�@�t�����݂X-�Lדa��N4��qܞ'$f0@� @V�nA�ܘY�L9:�|/^s��� ��)0`�j��T\w�uZ-����¨\� @�:��c�t���{�-��Rb��1%��I,Y%T���~ ��r �1����C��,�$��*ˀ���f<��0z����h�F��������| ���8Z-�CR����Tg��HRf��glY����s��-��p��'+����m�_ؒg������C�{� ����Ȫ�ϏΙ3g�-�GR|׹7`G��񥡘�0�U��_ٵZЏ�د�D�)���\>����ʗ������zN���@��~~��-��P��{rs���@�<����|.]�Ը|��m|g����_��y�W�KD1�b�M���%�s\����r �1��n�\ �ƒ�"-��` .4��~%3��I}[0A��$��=-�>BH"G�ۏ�^r��<�EBG�i�%���9�@^�~~ @�����1�� ��@�t�-[����{%@C�$�mAg���Κ5kʆх����/双O��l��ӿ��B�@.X���u�p�O��6��x�9MPn�`߷o_���^n�`t� 18 | ��(�����\r��s�A�y���ۂ�T��@h 19 | �E0l�0��;�tڵӘkƸN����Y�jU�� 20 | S#�|^㽺- |��p� N�.���ޥ`�^{�zL�6��4�ě�b��e�]&"�d�sΜ9Uޥ�U0�! ��*nP�*`���o֨v����i8G�����hh��m������ɓ�s�=�{J�U0�Ղ���wZ������������8bEz���,Y�D��![C�>}��7:k׮ �no��f�>jvR?#b��X �(��F�AT�F��i��[�{��zv��>��C���a+�[0B2�D��=��G~�( 21 | �ĺ������LO�\s�܂>"8|�`[) 22 | &Lp8�'��������4oGe�#�ۏ�lْ_\�D̀܂�2Z�l��i�9�� t�ȑ9f ޢ�-����=���Y�y��n?uQ�}Xͬ �sA�i>=��1�=R��+� + �܂��.2� �K������CƢۃ20h� �˫%53�5@�MA�%���̣������j[��9�;��_(�����0��~r���\�{�m�P����x#TT9��n?����N#��ץ&� }���) 23 | �T�VL�!���j���` �p �8@Rr�UAV�A����=��-����pLH�`@n�*Ȋ1�܂U���?}w ]�H2@�ߴi��V���[�˯%�������5�8�)Э 24 | T`��|rZbZ-�.�!da+@����ߞ�Z�gf�[0p������ I��gr �$��o%P�_rCy �V�|߽����"m�Y���-�[ l��kxA���ۯ9]�[pҤI�Ȩ�pP���k��Feِ���gHE�d�nAm"Z�$��5} ���z�8����2r�X�|� ��Sܻw��r�J�s�J�~�T�f�z{�ͫ��x�j?j��Q�E�n��js���|G�xз���󕾤�rzr�� ��`���V{���u��4448�V��ra��p���QRZ�<{�dK.F9��#~T���s.����N%*� ���Ýu�8G&����/W:*x%�{�}@� ��l���Nc#�AI�������i����*?�د�0}�g���C"Ā pۯ������4薒ҏ(b�8�_Q�Y� ���r7'��� `��� �j�6�� *��3�W�g��"��l� �ˆ1�:�Sg}%� � ��P?����1`�����Y���"��D�0b@ �������9������[t��F1���p`k�\U�`��R��A#W81 e`)R�ZM�����[ u��F0� rq.�����#^�=C"Ā9 P'�R~f��� 27 | pn�zdC"�e���?�\K����@&$b }jz�3۵�x/{ ��1 Ra�#�|��ƟUK�=&�^��TM�n�2�9�5)?s���{O'�D ��D���o[kM�oK0�x���Td�_@]b r� �G�����;����D ��D���1�gaR �`��'`0�  �> \��/���f��������ŀ����!fn�Z�|b����U�.t���ट���r�9�+�������� �b rnE�Dk�=��8�����!b R�Cl�P�E�`�܌�K�'~�@���}*�!`�@��6L� �;�� $b@ D��?#��g�F� 28 | �� V��1�v��;�Es��Q����=ɮ�4���b@ T��n��!��3q�0^�V��c��1�ܶ��[����M�=8I����1@�څ@Cu��`N�o��WJĀ�W����e��I��n��N�mீ��ܴ�_ d��(�4`E܅I�� ��"̵�1 *3�+\�E� �\M���)g r��� 29 | ���8�>��p�?vI��0�ǀ~�!b������$'�%"I����R��i�1 �0� �?S~&���r��� ��{ n�_ �����L�?��T�e��Ǝ�7�C"r��OQ~"qI���O 8�?$b � ܋r�#@�_�v�J̙��/��3�'d�/����W[����o'N� �l� �-2����@j�O~��0���2`H�@�؄��+����p OB��uO��(l�S�ԕ���9����~�c�:x/�X d�.���Ɣ�d��V�y@F$H2�����+M*�i��l8O@F$H2����2�4&r� PO��֢��€��7N�YS ����Y�1`��;�JS3n� g[�'��@W@"la`32�n?'�HB2p 30 | �hām�mu �����j@F@��V����Z!��xI���H�y�ѱ) ��>��Z!6���a�`�����dDV$9f��� pM�6�I�!LG:\LdrwPy�~�P�%��L3��7�TK��Am�mo|�6�� 3��-�hJ3��?�67 �yr���"�� ��g��4.$�1���_�[*��&���S/�dq������� C��h�3��>�6Ŷ%������\�#�RZq� � =lK|ŔX��X�WS�ej5/����$���:��v@������8�� �d��1(�z2~F�)���3��͋���l��C�������#����=�.\Lt? %�N$9b�%�:���2��u �1|-� ld�����t$b��@?���@� �F�c��ρ^�D �d�[9�ࠐz�����: 31 | H�@ ��P2v)~���@����z5��|����R�ֵ���|`#�W39؂��<�"-�0��\<�d ��u�oGLz1��Gp����e�倯d�.�j H�@j �F�3��@ c{s<��J& �@�����b���w�� �� ��n���v��< �����,M;��*p>p!0hH��{=�����x�]I� �DLh����<'��h8�@V �#��J���f�I� ��Hn����W�} �N�t[u�$��������� @� 2 �]&)� �#�3���, =%�T���k�&� I�����I��ӳ��[8 � �L�]�]t�T�g���6�-@b2U�OV��: A?�� } .i�| �xC���rv�w;��#�>�i8_b82�WP�������{'n���8�z;�Ƥy��s���@���P��o|�S�ih$3��@߹j��IEND�B`� -------------------------------------------------------------------------------- /docs/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/thirteen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/theme.config.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useRouter } from "next/router"; 3 | 4 | // eslint-disable-next-line import/no-anonymous-default-export 5 | export default { 6 | useNextSeoProps() { 7 | const { asPath } = useRouter(); 8 | if (asPath !== "/") { 9 | return { 10 | titleTemplate: "%s • fp_", 11 | }; 12 | } else { 13 | return { 14 | title: "fp_", 15 | }; 16 | } 17 | }, 18 | logo: fp_, 19 | docsRepositoryBase: "https://github.com/nexxeln/fp/docs/pages", 20 | feedback: { 21 | content: null, 22 | }, 23 | project: { 24 | link: "https://github.com/nexxeln/fp", 25 | }, 26 | // ... 27 | }; 28 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "~/*": ["./*"] 20 | } 21 | }, 22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /functions/compose.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Composes functions into one function from left to right. 3 | * 4 | * @example 5 | * ``` 6 | * type Person = { name: string; age: number }; 7 | * 8 | * const fn = compose( 9 | * (p: Person) => ({ ...p, age: p.age + 1 }), 10 | * (p) => ({ ...p, name: p.name.toUpperCase() }) 11 | * ); 12 | * 13 | * 14 | * const result = fn({ name: "John", age: 20 }); 15 | * 16 | * assertEquals(result, { name: "JOHN", age: 21 }); 17 | * ``` 18 | */ 19 | export function compose, B>( 20 | fn1: (...a: A) => B 21 | ): (...a: A) => B; 22 | export function compose, B, C>( 23 | fn1: (...a: A) => B, 24 | fn2: (b: B) => C 25 | ): (...a: A) => C; 26 | export function compose, B, C, D>( 27 | fn1: (...a: A) => B, 28 | fn2: (b: B) => C, 29 | fn3: (c: C) => D 30 | ): (...a: A) => D; 31 | export function compose, B, C, D, E>( 32 | fn1: (...a: A) => B, 33 | fn2: (b: B) => C, 34 | fn3: (c: C) => D, 35 | fn4: (d: D) => E 36 | ): (...a: A) => E; 37 | export function compose, B, C, D, E, F>( 38 | fn1: (...a: A) => B, 39 | fn2: (b: B) => C, 40 | fn3: (c: C) => D, 41 | fn4: (d: D) => E, 42 | fn5: (e: E) => F 43 | ): (...a: A) => F; 44 | export function compose, B, C, D, E, F, G>( 45 | fn1: (...a: A) => B, 46 | fn2: (b: B) => C, 47 | fn3: (c: C) => D, 48 | fn4: (d: D) => E, 49 | fn5: (e: E) => F, 50 | fn6: (f: F) => G 51 | ): (...a: A) => G; 52 | export function compose, B, C, D, E, F, G, H>( 53 | fn1: (...a: A) => B, 54 | fn2: (b: B) => C, 55 | fn3: (c: C) => D, 56 | fn4: (d: D) => E, 57 | fn5: (e: E) => F, 58 | fn6: (f: F) => G, 59 | fn7: (g: G) => H 60 | ): (...a: A) => H; 61 | export function compose< 62 | A extends ReadonlyArray, 63 | B, 64 | C, 65 | D, 66 | E, 67 | F, 68 | G, 69 | H, 70 | I 71 | >( 72 | fn1: (...a: A) => B, 73 | fn2: (b: B) => C, 74 | fn3: (c: C) => D, 75 | fn4: (d: D) => E, 76 | fn5: (e: E) => F, 77 | fn6: (f: F) => G, 78 | fn7: (g: G) => H, 79 | fn8: (h: H) => I 80 | ): (...a: A) => I; 81 | export function compose< 82 | A extends ReadonlyArray, 83 | B, 84 | C, 85 | D, 86 | E, 87 | F, 88 | G, 89 | H, 90 | I, 91 | J 92 | >( 93 | fn1: (...a: A) => B, 94 | fn2: (b: B) => C, 95 | fn3: (c: C) => D, 96 | fn5: (e: E) => F, 97 | fn6: (f: F) => G, 98 | fn7: (g: G) => H, 99 | fn8: (h: H) => I, 100 | fn9: (i: I) => J 101 | ): (...a: A) => J; 102 | export function compose< 103 | A extends ReadonlyArray, 104 | B, 105 | C, 106 | D, 107 | E, 108 | F, 109 | G, 110 | H, 111 | I, 112 | J, 113 | K 114 | >( 115 | fn1: (...a: A) => B, 116 | fn2: (b: B) => C, 117 | fn3: (c: C) => D, 118 | fn5: (e: E) => F, 119 | fn6: (f: F) => G, 120 | fn7: (g: G) => H, 121 | fn8: (h: H) => I, 122 | fn9: (i: I) => J, 123 | fn10: (j: J) => K 124 | ): (...a: A) => K; 125 | export function compose< 126 | A extends ReadonlyArray, 127 | B, 128 | C, 129 | D, 130 | E, 131 | F, 132 | G, 133 | H, 134 | I, 135 | J, 136 | K, 137 | L 138 | >( 139 | fn1: (...a: A) => B, 140 | fn2: (b: B) => C, 141 | fn3: (c: C) => D, 142 | fn5: (e: E) => F, 143 | fn6: (f: F) => G, 144 | fn7: (g: G) => H, 145 | fn8: (h: H) => I, 146 | fn9: (i: I) => J, 147 | fn10: (j: J) => K, 148 | fn11: (k: K) => L 149 | ): (...a: A) => L; 150 | export function compose< 151 | A extends ReadonlyArray, 152 | B, 153 | C, 154 | D, 155 | E, 156 | F, 157 | G, 158 | H, 159 | I, 160 | J, 161 | K, 162 | L, 163 | M 164 | >( 165 | fn1: (...a: A) => B, 166 | fn2: (b: B) => C, 167 | fn3: (c: C) => D, 168 | fn5: (e: E) => F, 169 | fn6: (f: F) => G, 170 | fn7: (g: G) => H, 171 | fn8: (h: H) => I, 172 | fn9: (i: I) => J, 173 | fn10: (j: J) => K, 174 | fn11: (k: K) => L, 175 | fn12: (k: L) => M 176 | ): (...a: A) => M; 177 | // deno-lint-ignore ban-types 178 | export function compose(fn1: Function, ...fns: Function[]) { 179 | if (fns.length === 0) { 180 | return fn1; 181 | } 182 | 183 | return (...args: unknown[]) => 184 | fns.reduce((prev, fn) => fn(prev), fn1(...args)); 185 | } 186 | -------------------------------------------------------------------------------- /functions/compose_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts"; 2 | 3 | import { compose } from "./compose.ts"; 4 | 5 | Deno.test("compose - single function", () => { 6 | const addOne = (n: number) => n + 1; 7 | 8 | const fn = compose(addOne); 9 | 10 | const result = fn(0); 11 | 12 | assertEquals(result, 1); 13 | }); 14 | 15 | Deno.test("compose - multiple functions", () => { 16 | const addOne = (n: number) => n + 1; 17 | const addTwo = (n: number) => n + 2; 18 | const timesTen = (n: number) => n * 10; 19 | 20 | const fn = compose(addOne, addTwo, timesTen); 21 | 22 | const result = fn(1); 23 | 24 | assertEquals(result, 40); 25 | }); 26 | 27 | Deno.test("compose - string manipulation", () => { 28 | const toUpperCase = (str: string) => str.toUpperCase(); 29 | const addExclamation = (str: string) => `${str}!`; 30 | 31 | const fn = compose(toUpperCase, addExclamation); 32 | 33 | const result = fn("hello"); 34 | 35 | assertEquals(result, "HELLO!"); 36 | }); 37 | 38 | Deno.test("compose - array manipulation", () => { 39 | const fn = compose( 40 | (arr: string[]) => arr.map((char) => char.toUpperCase()), 41 | (arr) => arr.map((char) => `${char}!`), 42 | (arr) => arr.join("") 43 | ); 44 | 45 | const result = fn(["h", "e", "l", "l", "o"]); 46 | 47 | assertEquals(result, "H!E!L!L!O!"); 48 | }); 49 | 50 | Deno.test("compose - object manipulation", () => { 51 | type Person = { name: string; age: number }; 52 | 53 | const fn = compose( 54 | (p: Person) => ({ ...p, age: p.age + 1 }), 55 | (p) => ({ ...p, name: p.name.toUpperCase() }) 56 | ); 57 | 58 | const result = fn({ name: "John", age: 20 }); 59 | 60 | assertEquals(result, { name: "JOHN", age: 21 }); 61 | }); 62 | -------------------------------------------------------------------------------- /functions/pipe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Pipes the value of an expression into a pipeline of functions. 3 | * 4 | * @example 5 | * ``` 6 | * type Person = { name: string; age: number }; 7 | * 8 | * const result = pipe( 9 | * { name: "John", age: 20} 10 | * (p: Person) => ({ ...p, age: p.age + 1 }), 11 | * (p) => ({ ...p, name: p.name.toUpperCase() }) 12 | * ); 13 | * 14 | * assertEquals(result, { name: "JOHN", age: 21 }); 15 | * ``` 16 | */ 17 | export function pipe(value: A): A; 18 | export function pipe(value: A, fn1: (input: A) => B): B; 19 | export function pipe( 20 | value: A, 21 | fn1: (input: A) => B, 22 | fn2: (input: B) => C 23 | ): C; 24 | export function pipe( 25 | value: A, 26 | fn1: (input: A) => B, 27 | fn2: (input: B) => C, 28 | fn3: (input: C) => D 29 | ): D; 30 | export function pipe( 31 | value: A, 32 | fn1: (input: A) => B, 33 | fn2: (input: B) => C, 34 | fn3: (input: C) => D, 35 | fn4: (input: D) => E 36 | ): E; 37 | export function pipe( 38 | value: A, 39 | fn1: (input: A) => B, 40 | fn2: (input: B) => C, 41 | fn3: (input: C) => D, 42 | fn4: (input: D) => E, 43 | fn5: (input: E) => F 44 | ): F; 45 | export function pipe( 46 | value: A, 47 | fn1: (input: A) => B, 48 | fn2: (input: B) => C, 49 | fn3: (input: C) => D, 50 | fn4: (input: D) => E, 51 | fn5: (input: E) => F, 52 | fn6: (input: F) => G 53 | ): G; 54 | export function pipe( 55 | value: A, 56 | fn1: (input: A) => B, 57 | fn2: (input: B) => C, 58 | fn3: (input: C) => D, 59 | fn4: (input: D) => E, 60 | fn5: (input: E) => F, 61 | fn6: (input: F) => G, 62 | fn7: (input: G) => H 63 | ): H; 64 | export function pipe( 65 | value: A, 66 | fn1: (input: A) => B, 67 | fn2: (input: B) => C, 68 | fn3: (input: C) => D, 69 | fn4: (input: D) => E, 70 | fn5: (input: E) => F, 71 | fn6: (input: F) => G, 72 | fn7: (input: G) => H, 73 | fn8: (input: H) => I 74 | ): I; 75 | export function pipe( 76 | value: A, 77 | fn1: (input: A) => B, 78 | fn2: (input: B) => C, 79 | fn3: (input: C) => D, 80 | fn4: (input: D) => E, 81 | fn5: (input: E) => F, 82 | fn6: (input: F) => G, 83 | fn7: (input: G) => H, 84 | fn8: (input: H) => I, 85 | fn9: (input: I) => J 86 | ): J; 87 | export function pipe( 88 | value: A, 89 | fn1: (input: A) => B, 90 | fn2: (input: B) => C, 91 | fn3: (input: C) => D, 92 | fn4: (input: D) => E, 93 | fn5: (input: E) => F, 94 | fn6: (input: F) => G, 95 | fn7: (input: G) => H, 96 | fn8: (input: H) => I, 97 | fn9: (input: I) => J, 98 | fn10: (input: J) => K 99 | ): K; 100 | export function pipe( 101 | value: A, 102 | fn1: (input: A) => B, 103 | fn2: (input: B) => C, 104 | fn3: (input: C) => D, 105 | fn4: (input: D) => E, 106 | fn5: (input: E) => F, 107 | fn6: (input: F) => G, 108 | fn7: (input: G) => H, 109 | fn8: (input: H) => I, 110 | fn9: (input: I) => J, 111 | fn10: (input: J) => K, 112 | fn11: (input: K) => L 113 | ): L; 114 | export function pipe( 115 | value: A, 116 | fn1: (input: A) => B, 117 | fn2: (input: B) => C, 118 | fn3: (input: C) => D, 119 | fn4: (input: D) => E, 120 | fn5: (input: E) => F, 121 | fn6: (input: F) => G, 122 | fn7: (input: G) => H, 123 | fn8: (input: H) => I, 124 | fn9: (input: I) => J, 125 | fn10: (input: J) => K, 126 | fn11: (input: K) => L, 127 | fn12: (input: L) => M 128 | ): M; 129 | 130 | // deno-lint-ignore ban-types 131 | export function pipe(value: unknown, ...fns: Function[]): unknown { 132 | return fns.reduce((acc, fn) => fn(acc), value); 133 | } 134 | -------------------------------------------------------------------------------- /functions/pipe_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts"; 2 | 3 | import { pipe } from "./pipe.ts"; 4 | 5 | // chatgpt wrote these tests because i cba to write them myself 6 | 7 | Deno.test("pipe - single function", () => { 8 | const addOne = (num: number) => num + 1; 9 | 10 | const result = pipe(0, addOne); 11 | 12 | assertEquals(result, 1); 13 | }); 14 | 15 | Deno.test("pipe - multiple functions", () => { 16 | const addOne = (num: number) => num + 1; 17 | const double = (num: number) => num * 2; 18 | 19 | const result = pipe(0, addOne, double); 20 | 21 | assertEquals(result, 2); 22 | }); 23 | 24 | Deno.test("pipe - string manipulation", () => { 25 | const toUpperCase = (str: string) => str.toUpperCase(); 26 | const addExclamation = (str: string) => `${str}!`; 27 | 28 | const result = pipe("hello", toUpperCase, addExclamation); 29 | 30 | assertEquals(result, "HELLO!"); 31 | }); 32 | 33 | Deno.test("pipe - array manipulation", () => { 34 | const addOne = (num: number) => num + 1; 35 | const double = (num: number) => num * 2; 36 | 37 | const result = pipe( 38 | [0, 1, 2], 39 | (x) => x.map((i) => addOne(i)), 40 | (x) => x.map((i) => double(i)) 41 | ); 42 | 43 | assertEquals(result, [2, 4, 6]); 44 | }); 45 | 46 | Deno.test("pipe - object manipulation", () => { 47 | type Person = { name: string; age: number }; 48 | 49 | const result = pipe( 50 | { name: "John", age: 20 }, 51 | (p: Person) => ({ ...p, age: p.age + 1 }), 52 | (p) => ({ ...p, name: p.name.toUpperCase() }) 53 | ); 54 | 55 | assertEquals(result, { name: "JOHN", age: 21 }); 56 | }); 57 | 58 | Deno.test("pipe - empty function list", () => { 59 | const result = pipe("hello"); 60 | 61 | assertEquals(result, "hello"); 62 | }); 63 | --------------------------------------------------------------------------------