├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── lerna.json ├── package.json ├── packages ├── either │ ├── README.md │ ├── package.json │ └── src │ │ ├── Either.ts │ │ ├── Left.ts │ │ ├── Right.ts │ │ ├── ap.ts │ │ ├── chain.test.ts │ │ ├── chain.ts │ │ ├── chainLeft.ts │ │ ├── index.ts │ │ ├── map.ts │ │ ├── mapLeft.ts │ │ ├── swap.ts │ │ └── unpack │ │ ├── index.ts │ │ ├── types.ts │ │ ├── unpack.test.ts │ │ └── unpack.ts ├── functions │ ├── README.md │ ├── package.json │ └── src │ │ ├── always │ │ ├── always.ts │ │ └── index.ts │ │ ├── apply │ │ ├── apply.test.ts │ │ ├── apply.ts │ │ ├── index.ts │ │ └── types.ts │ │ ├── compose │ │ ├── index.ts │ │ └── types.ts │ │ ├── curry │ │ ├── curry.ts │ │ ├── curry2.ts │ │ ├── curry3.ts │ │ ├── curry4.ts │ │ ├── curry5.ts │ │ └── index.ts │ │ ├── curryN │ │ ├── index.ts │ │ └── types.ts │ │ ├── flip │ │ ├── flip.ts │ │ ├── index.ts │ │ └── types.ts │ │ ├── id │ │ └── index.ts │ │ ├── index.ts │ │ ├── memoize │ │ ├── index.ts │ │ ├── memoize.test.ts │ │ └── memoize.ts │ │ ├── partial │ │ ├── index.ts │ │ ├── partial.test.ts │ │ ├── partial.ts │ │ └── types.ts │ │ ├── pipe │ │ ├── index.ts │ │ ├── pipe.test.ts │ │ ├── pipe.ts │ │ └── types.ts │ │ └── types.ts ├── future │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Future.test.ts │ │ ├── Future.ts │ │ ├── all.test.ts │ │ ├── all.ts │ │ ├── ap.ts │ │ ├── chain.test.ts │ │ ├── chain.ts │ │ ├── chainLeft.ts │ │ ├── fork.test.ts │ │ ├── fork.ts │ │ ├── index.ts │ │ ├── map.ts │ │ ├── mapLeft.ts │ │ ├── sequence.test.ts │ │ ├── sequence.ts │ │ └── toPromise.ts │ └── yarn.lock ├── lenses │ ├── README.md │ ├── package.json │ └── src │ │ ├── composeLenses │ │ ├── composeLenses.ts │ │ ├── index.ts │ │ └── types.ts │ │ ├── index.ts │ │ ├── lens │ │ ├── index.ts │ │ └── lens.ts │ │ ├── pipeLenses │ │ ├── index.ts │ │ ├── pipeLenses.test.ts │ │ ├── pipeLenses.ts │ │ └── types.ts │ │ ├── types.ts │ │ ├── updateAt │ │ └── index.ts │ │ └── view │ │ ├── index.ts │ │ └── view.test.ts ├── list │ ├── README.md │ ├── package.json │ └── src │ │ ├── ap │ │ └── index.ts │ │ ├── append │ │ ├── append.test.ts │ │ ├── append.ts │ │ └── index.ts │ │ ├── arrayFrom │ │ ├── arrayFrom.ts │ │ └── index.ts │ │ ├── ascend │ │ ├── ascend.test.ts │ │ └── index.ts │ │ ├── chain │ │ └── index.ts │ │ ├── concat │ │ ├── concat.test.ts │ │ ├── concat.ts │ │ ├── index.ts │ │ └── types.ts │ │ ├── contains │ │ └── index.ts │ │ ├── descend │ │ └── index.ts │ │ ├── drop │ │ ├── drop.ts │ │ └── index.ts │ │ ├── dropLast │ │ ├── dropLast.ts │ │ └── index.ts │ │ ├── endsWith │ │ ├── endsWith.test.ts │ │ ├── endsWith.ts │ │ └── index.ts │ │ ├── filter │ │ ├── filter.ts │ │ ├── index.ts │ │ └── types.ts │ │ ├── find │ │ └── index.ts │ │ ├── findIndex │ │ ├── findIndex.test.ts │ │ ├── findIndex.ts │ │ └── index.ts │ │ ├── findLast │ │ └── index.ts │ │ ├── findLastIndex │ │ ├── findLastIndex.ts │ │ └── index.ts │ │ ├── flatten │ │ ├── flatten.test.ts │ │ ├── flatten.ts │ │ ├── index.ts │ │ └── types.ts │ │ ├── forEach │ │ └── index.ts │ │ ├── groupBy │ │ ├── groupBy.ts │ │ ├── index.ts │ │ └── types.ts │ │ ├── head │ │ └── index.ts │ │ ├── includes │ │ ├── includes.test.ts │ │ ├── includes.ts │ │ └── index.ts │ │ ├── index.ts │ │ ├── indexOf │ │ ├── index.ts │ │ ├── indexOf.ts │ │ └── types.ts │ │ ├── insert │ │ ├── index.ts │ │ ├── insert.ts │ │ └── types.ts │ │ ├── isList │ │ └── index.ts │ │ ├── join │ │ ├── index.ts │ │ ├── join.test.ts │ │ └── join.ts │ │ ├── last │ │ └── index.ts │ │ ├── lastIndexOf │ │ └── index.ts │ │ ├── length │ │ └── index.ts │ │ ├── lensIndex │ │ └── index.ts │ │ ├── map │ │ └── index.ts │ │ ├── move │ │ ├── index.ts │ │ ├── move.test.ts │ │ └── move.ts │ │ ├── prepend │ │ ├── index.ts │ │ └── prepend.test.ts │ │ ├── range │ │ ├── index.ts │ │ ├── range.test.ts │ │ └── range.ts │ │ ├── reduce │ │ ├── index.ts │ │ ├── reduce.ts │ │ └── types.ts │ │ ├── reduceRight │ │ ├── index.ts │ │ └── types.ts │ │ ├── remove │ │ ├── index.ts │ │ ├── remove.test.ts │ │ ├── remove.ts │ │ └── types.ts │ │ ├── reverse │ │ ├── index.ts │ │ ├── reverse.test.ts │ │ ├── reverse.ts │ │ └── types.ts │ │ ├── slice │ │ └── index.ts │ │ ├── sort │ │ └── index.ts │ │ ├── splitAt │ │ ├── index.ts │ │ └── splitAt.ts │ │ ├── splitEvery │ │ └── index.ts │ │ ├── startsWith │ │ ├── index.ts │ │ ├── startsWith.test.ts │ │ └── startsWith.ts │ │ ├── take │ │ └── index.ts │ │ ├── takeLast │ │ └── index.ts │ │ ├── types.ts │ │ ├── uniq │ │ └── index.ts │ │ ├── update │ │ ├── index.ts │ │ ├── types.ts │ │ ├── update.test.ts │ │ └── update.ts │ │ └── without │ │ └── index.ts ├── logic │ ├── README.md │ ├── package.json │ └── src │ │ ├── all │ │ └── index.ts │ │ ├── allPass │ │ └── index.ts │ │ ├── and │ │ └── index.ts │ │ ├── any │ │ ├── any.ts │ │ ├── index.ts │ │ └── types.ts │ │ ├── anyPass │ │ └── index.ts │ │ ├── cond │ │ ├── cond.ts │ │ ├── index.ts │ │ └── types.ts │ │ ├── equals │ │ ├── equals.test.ts │ │ ├── equals.ts │ │ ├── functionName │ │ │ ├── functionName.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── typeOf │ │ │ ├── index.ts │ │ │ ├── typeOf.test.ts │ │ │ └── typeOf.ts │ │ └── types.ts │ │ ├── greaterThan │ │ └── index.ts │ │ ├── greaterThanOrEqual │ │ └── index.ts │ │ ├── ifElse │ │ ├── ifElse.ts │ │ ├── index.ts │ │ └── types.ts │ │ ├── index.ts │ │ ├── is │ │ ├── index.ts │ │ ├── isArray.ts │ │ ├── isIterable.ts │ │ ├── isIterator.ts │ │ ├── isList.ts │ │ ├── isMap.ts │ │ ├── isNull.ts │ │ ├── isNumber.ts │ │ ├── isObject.ts │ │ ├── isPromiseLike.ts │ │ ├── isSet.ts │ │ └── isUndefined.ts │ │ ├── lessThan │ │ └── index.ts │ │ ├── lessThanOrEqual │ │ └── index.ts │ │ ├── not │ │ └── index.ts │ │ ├── or │ │ └── index.ts │ │ ├── propEq │ │ ├── index.ts │ │ ├── propEq.test.ts │ │ ├── propEq.ts │ │ └── types.ts │ │ ├── propOr │ │ ├── index.ts │ │ └── propOr.ts │ │ ├── tryCatch │ │ ├── index.ts │ │ ├── tryCatch.ts │ │ └── types.ts │ │ └── types.ts ├── math │ ├── README.md │ ├── package.json │ └── src │ │ ├── add │ │ ├── add.ts │ │ └── index.ts │ │ ├── decrement │ │ └── index.ts │ │ ├── divide │ │ └── index.ts │ │ ├── increment │ │ └── index.ts │ │ ├── index.ts │ │ ├── mean │ │ ├── index.ts │ │ └── mean.test.ts │ │ ├── median │ │ ├── index.ts │ │ ├── median.test.ts │ │ └── median.ts │ │ ├── modulo │ │ └── index.ts │ │ ├── multiply │ │ └── index.ts │ │ ├── negate │ │ └── index.ts │ │ ├── pow │ │ └── index.ts │ │ ├── product │ │ └── index.ts │ │ ├── subtract │ │ └── index.ts │ │ └── sum │ │ └── index.ts ├── maybe │ ├── README.md │ ├── package.json │ └── src │ │ ├── Just.ts │ │ ├── Maybe.ts │ │ ├── Nothing.ts │ │ ├── ap.ts │ │ ├── chain.test.ts │ │ ├── chain.ts │ │ ├── combine.test.ts │ │ ├── combine.ts │ │ ├── combineArray.test.ts │ │ ├── combineArray.ts │ │ ├── fromJust.ts │ │ ├── fromMaybe.test.ts │ │ ├── fromMaybe.ts │ │ ├── index.ts │ │ ├── isJust.ts │ │ ├── isNothing.ts │ │ ├── map.ts │ │ ├── toMaybe.test.ts │ │ └── toMaybe.ts ├── objects │ ├── README.md │ ├── package.json │ └── src │ │ ├── clone │ │ ├── clone.test.ts │ │ ├── clone.ts │ │ ├── index.ts │ │ └── typeOf │ │ │ ├── index.ts │ │ │ ├── typeOf.test.ts │ │ │ └── typeOf.ts │ │ ├── hasOwnProperty │ │ ├── index.ts │ │ ├── invoker │ │ ├── index.ts │ │ ├── invoker.test.ts │ │ ├── invoker.ts │ │ └── types.ts │ │ ├── isEmpty │ │ ├── index.ts │ │ └── isEmpty.test.ts │ │ ├── keys │ │ ├── index.ts │ │ └── keys.ts │ │ ├── length │ │ └── index.ts │ │ ├── lensPath │ │ ├── index.ts │ │ ├── lensPath.test.ts │ │ ├── lensPath.ts │ │ └── types.ts │ │ ├── lensProp │ │ ├── index.ts │ │ └── lensProp.ts │ │ ├── path │ │ ├── index.ts │ │ ├── path.test.ts │ │ ├── path.ts │ │ └── types.ts │ │ ├── prop │ │ ├── index.ts │ │ ├── prop.test.ts │ │ ├── prop.ts │ │ └── types.ts │ │ ├── set │ │ ├── index.ts │ │ └── set.ts │ │ ├── types.ts │ │ └── values │ │ └── index.ts ├── prelude │ ├── README.md │ ├── package.json │ └── src │ │ ├── ap.ts │ │ ├── chain.test.ts │ │ ├── chain.ts │ │ ├── index.ts │ │ ├── map.test.ts │ │ ├── map.ts │ │ └── re-exports.ts ├── strings │ ├── README.md │ ├── package.json │ └── src │ │ ├── index.ts │ │ ├── split.ts │ │ ├── substr.ts │ │ ├── substring.ts │ │ ├── toLowerCase.ts │ │ ├── toUpperCase.ts │ │ └── trim.ts └── uuid │ ├── README.md │ ├── package.json │ ├── src │ ├── index.ts │ ├── isUuid.ts │ ├── randomUuidSeed │ │ ├── RandomNumberGenerator │ │ │ ├── BrowserGenerator.ts │ │ │ ├── NodeGenerator.ts │ │ │ ├── constants.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── isBrowser.ts │ │ ├── randomUuidSeed.test.ts │ │ ├── randomUuidSeed.ts │ │ └── types.ts │ ├── types.ts │ ├── uuid.ts │ └── uuid4 │ │ ├── index.ts │ │ ├── uuid.test.ts │ │ └── uuid4.ts │ └── yarn.lock ├── renovate.json ├── tools ├── build.js ├── build │ └── tsc.js ├── docs.js └── docs │ ├── findFiles.js │ ├── generateDocs.js │ └── parseDocumentation.js ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | insert_final_newline = false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # generated files 40 | lib 41 | lib.es2015 42 | .tmp 43 | 44 | # other stuff 45 | src/test.ts 46 | .idea 47 | .DS_Store 48 | .vscode 49 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TylorS/typed-unmaintained/ce54f35416a8057d345f4f5ca6caf4c761643176/.npmignore -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 8 3 | 4 | dist: trusty 5 | sudo: false 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Tylor Steinberger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Typed

2 | 3 |
4 | The TypeScript standard library 5 |
6 | 7 |
8 | 9 | ## Moved [Typed-Prelude](https://github.com/TylorS/typed-prelude) 10 | 11 | I've begun to rewrite Typed into a much larger project that addresses problem spaces not covered here. It's still quite early, and has little to no documentation, but the intention is to build the project up and hopefully even a community around it! If you're interested in helping please let me know! 12 | 13 | ## Packages 14 | 15 | There are many single purpose packages that make up `Typed`. 16 | 17 | - [`@typed/either`](./packages/either) -- Well-typed and functional `Either` 18 | - [`@typed/functions`](./packages/functions) -- Collection of functions for working with functions 19 | - [`@typed/future`](./packages/future) -- Well-typed and functional `Future` 20 | - [`@typed/lenses`](./packages/lenses) -- Well-typed functional lenses 21 | - [`@typed/list`](./packages/list) -- Immutable `List` for TypeScript 22 | - [`@typed/logic`](./packages/logic) -- Well-typed functions for performing logic 23 | - [`@typed/math`](./packages/math) -- Well-typed functions for performing math 24 | - [`@typed/maybe`](./packages/maybe) -- Well-typed and functional `Maybe` 25 | - [`@typed/objects`](./packages/objects) -- Collection of object-related functions 26 | - [`@typed/prelude`](./packages/prelude) -- Re-exports other `@typed` packages 27 | - [`@typed/strings`](./packages/strings) -- Collection of string manipulation functions 28 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "independent", 6 | "npmClient": "yarn" 7 | } 8 | -------------------------------------------------------------------------------- /packages/either/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/either", 3 | "version": "4.0.0", 4 | "description": "Well-typed Either data-structure", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only either", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:browser": "yarn test:unit -- --browser", 14 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 15 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 16 | }, 17 | "dependencies": { 18 | "@typed/functions": "3.0.0" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/TylorS/typed.git" 23 | }, 24 | "keywords": [ 25 | "Either", 26 | "typed" 27 | ], 28 | "author": "Tylor Steinberger ", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/TylorS/typed/issues" 32 | }, 33 | "homepage": "https://github.com/TylorS/typed#readme" 34 | } 35 | -------------------------------------------------------------------------------- /packages/either/src/Either.ts: -------------------------------------------------------------------------------- 1 | import { Left } from './Left' 2 | import { Right } from './Right' 3 | 4 | /** 5 | * Either data structure. Extremely useful for handling errors or different 6 | * logic paths without the use of if-statements. 7 | * @name Either 8 | * @type 9 | */ 10 | export type Either = Left | Right 11 | 12 | export namespace Either { 13 | /** 14 | * Creates an Either that is of type Right 15 | * @name Either.of(value: A): Either 16 | */ 17 | export const of: (value: A) => Either = Right.of 18 | /** 19 | * Creates an Either that is of type Left 20 | * @name Either.left(value: A): Either 21 | */ 22 | export const left: (value: A) => Either = Left.of 23 | } 24 | 25 | /** 26 | * Returns true if an Either is type Left 27 | * @name isLeft(either: Either): Either is Left 28 | */ 29 | export function isLeft(either: Either): either is Left { 30 | return either.hasOwnProperty('@typed/Left') 31 | } 32 | 33 | /** 34 | * Returns true if an Either is type Right 35 | * @name isRight(either: Either): either is Right 36 | */ 37 | export function isRight(either: Either): either is Right { 38 | return either.hasOwnProperty('@typed/Right') 39 | } 40 | -------------------------------------------------------------------------------- /packages/either/src/Left.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A JSON-serializable Left data-structure. 3 | * @name Left 4 | * @type 5 | */ 6 | export interface Left { 7 | readonly '@typed/Left': A 8 | } 9 | 10 | export namespace Left { 11 | /** 12 | * Create a Left 13 | * @name Left.of(value: A): Left 14 | */ 15 | export function of(value: A): Left { 16 | return { '@typed/Left': value } 17 | } 18 | } 19 | 20 | /** 21 | * Extracts the value contained in a Left. 22 | * @name fromLeft(left: Left): A 23 | */ 24 | export function fromLeft(left: Left): A { 25 | return left['@typed/Left'] 26 | } 27 | -------------------------------------------------------------------------------- /packages/either/src/Right.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A JSON-serializable Right data-structure. 3 | * @name Right 4 | * @type 5 | */ 6 | export interface Right { 7 | readonly '@typed/Right': A 8 | } 9 | 10 | export namespace Right { 11 | /** 12 | * Creates a Right 13 | * @name Right.of(value: A): Right 14 | * @param {A} 15 | * @return {Right} 16 | */ 17 | export function of(value: A): Right { 18 | return { '@typed/Right': value } 19 | } 20 | } 21 | 22 | /** 23 | * Extracts the value contained in a Right. 24 | * @name fromRight(right: Right): A 25 | * @param right 26 | */ 27 | export function fromRight(right: Right): A { 28 | return right['@typed/Right'] 29 | } 30 | -------------------------------------------------------------------------------- /packages/either/src/ap.ts: -------------------------------------------------------------------------------- 1 | import { Either } from './Either' 2 | import { chain } from './chain' 3 | import { curry2 } from '@typed/functions' 4 | import { map } from './map' 5 | 6 | /** 7 | * Applies the function contains in an `Either` to the value contained in a 8 | * second `Either`. 9 | * @name ap(fn: Either C>, value: Either): Either 10 | */ 11 | export const ap: EitherAp = curry2(__ap) 12 | 13 | function __ap(fn: Either C>, value: Either): Either { 14 | return chain(f => map(f, value), fn) 15 | } 16 | 17 | export type EitherAp = { 18 | (fn: Either C>, value: Either): Either 19 | (fn: Either C>): (value: Either) => Either 20 | } 21 | -------------------------------------------------------------------------------- /packages/either/src/chain.test.ts: -------------------------------------------------------------------------------- 1 | import { Either, chain, isLeft } from './' 2 | import { Test, describe, given, it } from '@typed/test' 3 | 4 | export const test: Test = describe(`chain`, [ 5 | given(`(b -> Either a c) and Left a`, [ 6 | it(`returns Left a and does not call function`, ({ equal, ok }) => { 7 | let callCount = 0 8 | 9 | function f() { 10 | ++callCount 11 | 12 | return Either.of(1) 13 | } 14 | 15 | const expectedCallCount = 0 16 | 17 | ok(isLeft(chain(f, Either.left(1)))) 18 | equal(expectedCallCount, callCount) 19 | }), 20 | ]), 21 | 22 | given(`(b -> Either a c) and Right b`, [ 23 | it(`returns result of (a -> Either a c)`, ({ equal, ok }) => { 24 | let callCount = 0 25 | const f = () => { 26 | ++callCount 27 | 28 | return Either.left(1) 29 | } 30 | 31 | const expectedCallCount = 1 32 | 33 | ok(isLeft(chain(f, Either.of(1)))) 34 | equal(expectedCallCount, callCount) 35 | }), 36 | ]), 37 | ]) 38 | -------------------------------------------------------------------------------- /packages/either/src/chain.ts: -------------------------------------------------------------------------------- 1 | import { Either, isLeft } from './Either' 2 | 3 | import { curry2 } from '@typed/functions' 4 | import { fromRight } from './Right' 5 | 6 | /** 7 | * Returns a `Either` that is the result of calling `f` with the resolved 8 | * value of another `Either`. 9 | * @name chain(f: (value: B) => Either, either: Either): Either 10 | */ 11 | export const chain: EitherChain = curry2(__chain) 12 | 13 | function __chain(f: (value: B) => Either, either: Either): Either { 14 | return isLeft(either) ? either : f(fromRight(either)) 15 | } 16 | 17 | export type EitherChain = { 18 | (f: (value: B) => Either, either: Either): Either 19 | (f: (value: B) => Either): (either: Either) => Either 20 | } 21 | -------------------------------------------------------------------------------- /packages/either/src/chainLeft.ts: -------------------------------------------------------------------------------- 1 | import { Either, isLeft } from './Either' 2 | 3 | import { curry2 } from '@typed/functions' 4 | import { fromLeft } from './Left' 5 | 6 | /** 7 | * Returns a `Either` that is the result of calling `f` with the rejected 8 | * value of another `Either`. 9 | * @name chainLeft(f: (value: B) => Either, either: Either): Either 10 | */ 11 | export const chainLeft: EitherChainLeft = curry2(__chainLeft) 12 | 13 | function __chainLeft(f: (value: A) => Either, either: Either): Either { 14 | return isLeft(either) ? f(fromLeft(either)) : either 15 | } 16 | 17 | export type EitherChainLeft = { 18 | (f: (value: A) => Either, either: Either): Either 19 | (f: (value: A) => Either): (either: Either) => Either 20 | } 21 | -------------------------------------------------------------------------------- /packages/either/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ap' 2 | export * from './chain' 3 | export * from './chainLeft' 4 | export * from './Either' 5 | export * from './Left' 6 | export * from './map' 7 | export * from './mapLeft' 8 | export * from './Right' 9 | export * from './swap' 10 | export * from './unpack' 11 | -------------------------------------------------------------------------------- /packages/either/src/map.ts: -------------------------------------------------------------------------------- 1 | import { Either } from './Either' 2 | import { chain } from './chain' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Returns a `Either` that is the result of calling `f` with the resolved 7 | * value of another `Either`. 8 | * @name map(f: (value: B) => C, either: Either): Either 9 | */ 10 | export const map: EitherMap = curry2(__map) 11 | 12 | function __map(f: (value: B) => C, either: Either): Either { 13 | return chain(value => Either.of(f(value)), either) 14 | } 15 | 16 | export type EitherMap = { 17 | (f: (value: B) => C, either: Either): Either 18 | (f: (value: B) => C): (either: Either) => Either 19 | } 20 | -------------------------------------------------------------------------------- /packages/either/src/mapLeft.ts: -------------------------------------------------------------------------------- 1 | import { Either } from './Either' 2 | import { chainLeft } from './chainLeft' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Returns a `Either` that is the result of calling `f` with the resolved 7 | * value of another `Either`. 8 | * @name mapLeft(f: (value: A) => C, either: Either): Either 9 | */ 10 | export const mapLeft: EitherMapLeft = curry2(__mapLeft) 11 | 12 | function __mapLeft(f: (value: A) => C, either: Either): Either { 13 | return chainLeft(value => Either.left(f(value)), either) 14 | } 15 | 16 | export type EitherMapLeft = { 17 | (f: (value: A) => C, either: Either): Either 18 | (f: (value: A) => C): (either: Either) => Either 19 | } 20 | -------------------------------------------------------------------------------- /packages/either/src/swap.ts: -------------------------------------------------------------------------------- 1 | import { Either, isLeft } from './Either' 2 | 3 | import { fromLeft } from './Left' 4 | import { fromRight } from './Right' 5 | 6 | /** 7 | * Swaps the values contained in an `Either`. 8 | * @name swap(either: Either): Either 9 | */ 10 | export function swap(either: Either): Either { 11 | return isLeft(either) ? Either.of(fromLeft(either)) : Either.left(fromRight(either)) 12 | } 13 | -------------------------------------------------------------------------------- /packages/either/src/unpack/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './unpack' 3 | -------------------------------------------------------------------------------- /packages/either/src/unpack/types.ts: -------------------------------------------------------------------------------- 1 | import { Arity1 } from '@typed/functions' 2 | import { Either } from '../Either' 3 | 4 | export type Unpack = { 5 | (f: Arity1, g: Arity1, either: Either): C 6 | (f: Arity1, g: Arity1): (either: Either) => C 7 | (f: Arity1): UnpackArity2A 8 | (f: Arity1): UnpackArity2B 9 | } 10 | 11 | export type UnpackArity2A = { 12 | (g: Arity1, either: Either): C 13 | (g: Arity1): (either: Either) => C 14 | } 15 | 16 | export type UnpackArity2B = { 17 | (g: Arity1, either: Either): C 18 | (g: Arity1): (either: Either) => C 19 | } 20 | -------------------------------------------------------------------------------- /packages/either/src/unpack/unpack.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { Either } from '../Either' 4 | import { unpack } from './unpack' 5 | 6 | export const test: Test = describe(`unpack`, [ 7 | given(`(a -> c) -> (b -> c) -> Left a`, [ 8 | it(`returns c by applying (a -> c)`, ({ same }) => { 9 | const x = Object.create(null) 10 | const y = Object.create(null) 11 | 12 | const f = (_: any) => x 13 | const g = (_: any) => y 14 | const either = Either.left(1) 15 | 16 | same(x, unpack(f, g, either)) 17 | }), 18 | ]), 19 | 20 | given(`(a -> c) -> (b -> c) -> Right b`, [ 21 | it(`returns c by applying (b -> c)`, ({ same }) => { 22 | const x = Object.create(null) 23 | const y = Object.create(null) 24 | 25 | const f = (_: any) => x 26 | const g = (_: any) => y 27 | const either = Either.of(1) 28 | 29 | same(y, unpack(f, g, either)) 30 | }), 31 | ]), 32 | ]) 33 | -------------------------------------------------------------------------------- /packages/either/src/unpack/unpack.ts: -------------------------------------------------------------------------------- 1 | import { Either, isLeft } from '../Either' 2 | 3 | import { Unpack } from './types' 4 | import { curry3 } from '@typed/functions' 5 | import { fromLeft } from '../Left' 6 | import { fromRight } from '../Right' 7 | 8 | /** 9 | * Extracts the value from an `Either` applying function `f` if the `Either` is 10 | * `Left` or function `g` if `Right`. 11 | * @name unpack(f: Arity1, g: Arity1, either: Either): C 12 | */ 13 | export const unpack: Unpack = curry3(__unpack) 14 | 15 | function __unpack(f: (value: A) => C, g: (value: B) => C, either: Either): C { 16 | return isLeft(either) ? f(fromLeft(either)) : g(fromRight(either)) 17 | } 18 | -------------------------------------------------------------------------------- /packages/functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/functions", 3 | "version": "3.0.0", 4 | "description": "Well-typed collection of functions for working with functions", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only functions", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:browser": "yarn test:unit -- --browser", 14 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 15 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/TylorS/typed.git" 20 | }, 21 | "keywords": [ 22 | "typed", 23 | "functions", 24 | "functional", 25 | "fp" 26 | ], 27 | "author": "Tylor Steinberger ", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/TylorS/typed/issues" 31 | }, 32 | "homepage": "https://github.com/TylorS/typed#readme" 33 | } 34 | -------------------------------------------------------------------------------- /packages/functions/src/always/always.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Given a value returns a function that will always return that value. 3 | * @name always(a: A): (...args: Array) => A 4 | */ 5 | export function always(a: A) { 6 | function constant(...args: Array): A 7 | function constant(): A { 8 | return a 9 | } 10 | 11 | return constant 12 | } 13 | -------------------------------------------------------------------------------- /packages/functions/src/always/index.ts: -------------------------------------------------------------------------------- 1 | export * from './always' 2 | -------------------------------------------------------------------------------- /packages/functions/src/apply/apply.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { apply } from './apply' 4 | 5 | const add = (x: number, y: number) => x + y 6 | 7 | export const test: Test = describe(`apply`, [ 8 | given(`a list of arguments and a function`, [ 9 | it(`calls the function with given arguments`, ({ equal }) => { 10 | const argumentArray = [1, 2, 3] 11 | const expected = argumentArray.reduce(add, 0) 12 | 13 | function f(x: number, y: number, z: number): number { 14 | equal(argumentArray, [x, y, z]) 15 | 16 | return x + y + z 17 | } 18 | 19 | equal(expected, apply(argumentArray, f)) 20 | }), 21 | ]), 22 | ]) 23 | -------------------------------------------------------------------------------- /packages/functions/src/apply/apply.ts: -------------------------------------------------------------------------------- 1 | import { Apply } from './types' 2 | 3 | /** 4 | * Given a list of arguments and a function, applies the function with 5 | * the given arguments. 6 | * @name apply(list: ArrayLike, fn: (...args: Array) => A): A 7 | */ 8 | export const apply: Apply = function(list: ArrayLike, f?: (...args: Array) => A) { 9 | if (!f) return (f: (...args: Array) => A) => __apply(list, f) 10 | 11 | return __apply(list, f) 12 | } 13 | 14 | function __apply(list: ArrayLike, f: (...args: Array) => A) { 15 | switch (list.length) { 16 | case 0: 17 | return f() 18 | case 1: 19 | return f(list[0]) 20 | case 2: 21 | return f(list[0], list[1]) 22 | case 3: 23 | return f(list[0], list[1], list[2]) 24 | case 4: 25 | return f(list[0], list[1], list[2], list[3]) 26 | case 5: 27 | return f(list[0], list[1], list[2], list[3], list[4]) 28 | default: 29 | return f.apply(null, list) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/functions/src/apply/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './apply' 3 | -------------------------------------------------------------------------------- /packages/functions/src/apply/types.ts: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | export type Apply = { 3 | (list: ArrayLike, f: () => A): A 4 | (list: [A], f: (a: A) => B): B 5 | (list: [A, B], f: (a: A, b: B) => C): C 6 | (list: [A, B, C], f: (a: A, b: B, c: C) => D): D 7 | (list: [A, B, C, D], f: (a: A, b: B, c: C, d: D) => E): E 8 | (list: [A, B, C, D, E], f: (a: A, b: B, c: C, d: D, e: E) => F): F 9 | 10 | (list: [A]): (f: (a: A) => B) => B 11 | (list: [A]): (f: (a: A) => B) => B 12 | 13 | (list: [A, B]): (f: (a: A, b: B) => C) => C 14 | (list: [A, B]): (f: (a: A, b: B) => C) => C 15 | 16 | (list: [A, B, C]): (f: (a: A, b: B, c: C) => D) => D 17 | (list: [A, B, C]): (f: (a: A, b: B, c: C) => D) => D 18 | 19 | (list: [A, B, C, D]): (f: (a: A, b: B, c: C, d: D) => E) => E 20 | (list: [A, B, C, D]): (f: (a: A, b: B, c: C, d: D) => E) => E 21 | 22 | (list: [A, B, C, D, E]): (f: (a: A, b: B, c: C, d: D, e: E) => F) => F 23 | 24 | (list: ArrayLike): (f: () => A) => A 25 | (list: ArrayLike): (f: (...args: Array) => A) => A 26 | (list: ArrayLike, f: (...args: Array) => A): A 27 | (list: ArrayLike): (f: (...args: Array) => A) => A 28 | } 29 | -------------------------------------------------------------------------------- /packages/functions/src/compose/index.ts: -------------------------------------------------------------------------------- 1 | import { Compose } from './types' 2 | import { apply } from '../apply' 3 | import { pipe } from '../pipe' 4 | 5 | /** 6 | * Right-to-left function composition. 7 | * @name compose(...fns: Array): (value: A) => B 8 | */ 9 | export const compose: Compose = (...fns: Array<(value: any) => any>) => apply(fns.reverse(), pipe) 10 | -------------------------------------------------------------------------------- /packages/functions/src/compose/types.ts: -------------------------------------------------------------------------------- 1 | import { Arity1 } from '../types' 2 | 3 | export type Compose = { 4 | (f: Arity1): Arity1 5 | (g: Arity1, f: Arity1): Arity1 6 | (h: Arity1, g: Arity1, f: Arity1): Arity1 7 | (i: Arity1, h: Arity1, g: Arity1, f: Arity1): Arity1 8 | ( 9 | j: Arity1, 10 | i: Arity1, 11 | h: Arity1, 12 | g: Arity1, 13 | f: Arity1 14 | ): Arity1 15 | ( 16 | k: Arity1, 17 | j: Arity1, 18 | i: Arity1, 19 | h: Arity1, 20 | g: Arity1, 21 | f: Arity1 22 | ): Arity1 23 | ( 24 | l: Arity1, 25 | k: Arity1, 26 | j: Arity1, 27 | i: Arity1, 28 | h: Arity1, 29 | g: Arity1, 30 | f: Arity1 31 | ): Arity1 32 | ( 33 | m: Arity1, 34 | l: Arity1, 35 | k: Arity1, 36 | j: Arity1, 37 | i: Arity1, 38 | h: Arity1, 39 | g: Arity1, 40 | f: Arity1 41 | ): Arity1 42 | 43 | (...fns: Array>): Arity1 44 | } 45 | -------------------------------------------------------------------------------- /packages/functions/src/curry/curry2.ts: -------------------------------------------------------------------------------- 1 | import { Arity2, Curry2 } from '../types' 2 | 3 | export function curry2(fn: Arity2): Curry2 4 | export function curry2(fn: Arity2): Curry2 5 | 6 | export function curry2(fn: Arity2): Curry2 { 7 | function curried(a: A, b: B): any { 8 | switch (arguments.length) { 9 | case 1: 10 | return (b: B) => fn(a, b) 11 | default: 12 | return fn(a, b) 13 | } 14 | } 15 | 16 | return curried as Curry2 17 | } 18 | -------------------------------------------------------------------------------- /packages/functions/src/curry/curry3.ts: -------------------------------------------------------------------------------- 1 | import { Arity3, Curry3 } from '../types' 2 | 3 | import { curry2 } from './curry2' 4 | 5 | export function curry3(fn: Arity3): Curry3 6 | export function curry3(fn: Arity3): Curry3 7 | 8 | export function curry3(fn: Arity3): Curry3 { 9 | function curried(a: A, b: B, c: C): any { 10 | switch (arguments.length) { 11 | case 1: 12 | return curry2((b: B, c: C) => fn(a, b, c)) 13 | case 2: 14 | return (c: C) => fn(a, b, c) 15 | default: 16 | return fn(a, b, c) 17 | } 18 | } 19 | 20 | return curried as Curry3 21 | } 22 | -------------------------------------------------------------------------------- /packages/functions/src/curry/curry4.ts: -------------------------------------------------------------------------------- 1 | import { Arity4, Curry4 } from '../types' 2 | 3 | import { curry2 } from './curry2' 4 | import { curry3 } from './curry3' 5 | 6 | export function curry4(fn: Arity4): Curry4 7 | export function curry4(fn: Arity4): Curry4 8 | 9 | export function curry4(fn: Arity4): Curry4 { 10 | function curried(a: A, b: B, c: C, d: D): any { 11 | switch (arguments.length) { 12 | case 1: 13 | return curry3((b: B, c: C, d: D) => fn(a, b, c, d)) 14 | case 2: 15 | return curry2((c: C, d: D) => fn(a, b, c, d)) 16 | case 3: 17 | return (d: D) => fn(a, b, c, d) 18 | default: 19 | return fn(a, b, c, d) 20 | } 21 | } 22 | 23 | return curried as Curry4 24 | } 25 | -------------------------------------------------------------------------------- /packages/functions/src/curry/curry5.ts: -------------------------------------------------------------------------------- 1 | import { Arity5, Curry5 } from '../types' 2 | 3 | import { curry2 } from './curry2' 4 | import { curry3 } from './curry3' 5 | import { curry4 } from './curry4' 6 | 7 | export function curry5(fn: Arity5): Curry5 8 | export function curry5( 9 | fn: Arity5 10 | ): Curry5 11 | 12 | export function curry5(fn: Arity5): Curry5 { 13 | function curried(a: A, b: B, c: C, d: D, e: E): any { 14 | switch (arguments.length) { 15 | case 1: 16 | return curry4((b: B, c: C, d: D, e: E) => fn(a, b, c, d, e)) 17 | case 2: 18 | return curry3((c: C, d: D, e: E) => fn(a, b, c, d, e)) 19 | case 3: 20 | return curry2((d: D, e: E) => fn(a, b, c, d, e)) 21 | case 4: 22 | return (e: E) => fn(a, b, c, d, e) 23 | default: 24 | return fn(a, b, c, d, e) 25 | } 26 | } 27 | 28 | return curried as Curry5 29 | } 30 | -------------------------------------------------------------------------------- /packages/functions/src/curry/index.ts: -------------------------------------------------------------------------------- 1 | export * from './curry' 2 | export * from './curry2' 3 | export * from './curry3' 4 | export * from './curry4' 5 | export * from './curry5' 6 | -------------------------------------------------------------------------------- /packages/functions/src/curryN/index.ts: -------------------------------------------------------------------------------- 1 | import { ArityN } from '../types' 2 | import { CurryNFn } from './types' 3 | 4 | /** 5 | * Curries a function to `n` arity. 6 | * @name curryN(arity: number, f: Function): CurriedFunction 7 | */ 8 | export const curryN: CurryNFn = curriedN( 9 | 2, 10 | (arity: number, f: ArityN) => curriedN(arity, f, []), 11 | [] 12 | ) 13 | 14 | function curriedN(arity: number, f: ArityN, previousArgs: Array): ArityN { 15 | if (arity <= 1) return f 16 | 17 | return function(...args: Array) { 18 | const concatArgs = previousArgs.concat(args) 19 | 20 | if (concatArgs.length >= arity) return f.apply(this, concatArgs) 21 | 22 | return curriedN(arity, f, concatArgs) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/functions/src/flip/flip.ts: -------------------------------------------------------------------------------- 1 | import { Flip } from './types' 2 | import { apply } from '../apply' 3 | import { curry } from '../curry' 4 | 5 | /** 6 | * Flips the first 2 arguments of a function. 7 | * @name flip(fn: (a: A, b: B) => C): Curry2 8 | */ 9 | export const flip: Flip = function flip(f: (a: A, b: B, ...args: Array) => C) { 10 | return curry(function(b: B, a: A, ...args: Array): C { 11 | return apply([a, b, ...args], f) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /packages/functions/src/flip/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './flip' 3 | -------------------------------------------------------------------------------- /packages/functions/src/flip/types.ts: -------------------------------------------------------------------------------- 1 | import * as t from '../types' 2 | 3 | export type Flip = { 4 | (f: t.Arity2): t.Curry2 5 | (f: t.Arity3): t.Curry3 6 | (f: t.Arity4): t.Curry4 7 | (f: t.Arity5): t.Curry5 8 | (f: t.Arity6): t.Curry6 9 | (f: t.Arity7): t.Curry7 10 | (f: t.Arity8): t.Curry8< 11 | B, 12 | A, 13 | C, 14 | D, 15 | E, 16 | F, 17 | G, 18 | H, 19 | I 20 | > 21 | (f: t.Arity9): t.Curry9< 22 | B, 23 | A, 24 | C, 25 | D, 26 | E, 27 | F, 28 | G, 29 | H, 30 | I, 31 | J 32 | > 33 | (f: t.Arity10): t.Curry10< 34 | B, 35 | A, 36 | C, 37 | D, 38 | E, 39 | F, 40 | G, 41 | H, 42 | I, 43 | J, 44 | K 45 | > 46 | } 47 | -------------------------------------------------------------------------------- /packages/functions/src/id/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the value passed in 3 | * @name id(value: A): A 4 | */ 5 | export const id: Id = (value: A): A => value 6 | 7 | export type Id = { 8 | (value: A, ...args: Array): A 9 | } 10 | -------------------------------------------------------------------------------- /packages/functions/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | 3 | export * from './always' 4 | export * from './apply' 5 | export * from './compose' 6 | export * from './curry' 7 | export * from './curryN' 8 | export * from './flip' 9 | export * from './id' 10 | export * from './memoize' 11 | export * from './partial' 12 | export * from './pipe' 13 | -------------------------------------------------------------------------------- /packages/functions/src/memoize/index.ts: -------------------------------------------------------------------------------- 1 | export * from './memoize' 2 | -------------------------------------------------------------------------------- /packages/functions/src/memoize/memoize.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { curry2 } from '../curry' 4 | import { memoize } from './memoize' 5 | 6 | export const test: Test = describe(`memoize`, [ 7 | given(`a function`, [ 8 | it(`returns a memoized function`, ({ equal }) => { 9 | let called = 0 10 | 11 | function f(a: number, b: number) { 12 | called += 1 13 | 14 | return a + b 15 | } 16 | 17 | const mf = memoize(f) 18 | 19 | equal(mf(1, 2), 3) 20 | equal(mf(1, 2), 3) 21 | equal(called, 1) 22 | }), 23 | ]), 24 | 25 | given(`a curried function`, [ 26 | it(`returns a memoized curried function`, ({ equal }) => { 27 | let called = 0 28 | 29 | function f(a: number, b: number) { 30 | called += 1 31 | 32 | return a + b 33 | } 34 | 35 | const c = curry2(f) 36 | 37 | const mf = memoize(c) 38 | 39 | equal(mf(1)(2), 3) 40 | equal(mf(1)(2), 3) 41 | 42 | equal(called, 1) 43 | }), 44 | ]), 45 | ]) 46 | -------------------------------------------------------------------------------- /packages/functions/src/memoize/memoize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Memoizes a function. 3 | * @name memoize(f: F): F 4 | */ 5 | export const memoize = function(f: F): F { 6 | const cache = new Map() 7 | 8 | return (function(...args: Array): any { 9 | const key = args.reduce((x, y) => x + JSON.stringify(y), '') 10 | 11 | if (cache.has(key)) return cache.get(key) 12 | 13 | let result = f.apply(this, args) 14 | 15 | if (typeof result === 'function') result = memoize(result) 16 | 17 | cache.set(key, result) 18 | 19 | return result 20 | } as any) as F 21 | } 22 | -------------------------------------------------------------------------------- /packages/functions/src/partial/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './partial' 3 | -------------------------------------------------------------------------------- /packages/functions/src/partial/partial.ts: -------------------------------------------------------------------------------- 1 | import { PartialFn, PlaceHolder } from './types' 2 | 3 | import { apply } from '../apply' 4 | import { curry2 } from '../curry' 5 | import { curryN } from '../curryN' 6 | 7 | /** 8 | * A placeholder for `partial`. 9 | * @name __ 10 | */ 11 | export const __: PlaceHolder = { '@@placeholder': true } 12 | 13 | const isPlaceholder = (x: any): x is PlaceHolder => x['@@placeholder'] === true 14 | 15 | /** 16 | * Allows partially applying a function 17 | * @name partial(f: Function, args: List): PartiallyAppliedFunction 18 | */ 19 | export const partial: PartialFn = curry2( 20 | (f: (...args: Array) => any, args: Array): any => { 21 | const fnLength = f.length 22 | const argsLength = args.length 23 | 24 | if (fnLength === 0) return f 25 | if (argsLength === 0) return curryN(fnLength as 2, f) 26 | 27 | const placeholderAmount = args.filter(isPlaceholder).length 28 | const expectedLength = Math.max(0, fnLength - argsLength) + placeholderAmount 29 | 30 | function partiallyApplied(...otherArgs: Array) { 31 | if (placeholderAmount === 0) return apply(args.concat(otherArgs), f) 32 | 33 | const combinedArgs: Array = Array(fnLength) 34 | 35 | for (let i = 0; i < combinedArgs.length; ++i) 36 | combinedArgs[i] = isPlaceholder(args[i]) ? otherArgs.shift() : args[i] 37 | 38 | return apply(combinedArgs.concat(otherArgs), f) 39 | } 40 | 41 | return curryN(expectedLength as 2, partiallyApplied) 42 | } 43 | ) 44 | -------------------------------------------------------------------------------- /packages/functions/src/pipe/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './pipe' 3 | -------------------------------------------------------------------------------- /packages/functions/src/pipe/pipe.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, it } from '@typed/test' 2 | 3 | import { pipe } from './pipe' 4 | 5 | export const test: Test = describe(`pipe`, [ 6 | it('pipes functions', ({ equal }) => { 7 | const f = (x: number) => x + 1 8 | const g = (x: number) => x * 2 9 | 10 | equal(pipe(f)(1), 2) 11 | equal( 12 | pipe( 13 | f, 14 | g 15 | )(1), 16 | 4 17 | ) 18 | equal( 19 | pipe( 20 | f, 21 | g, 22 | g 23 | )(1), 24 | 8 25 | ) 26 | equal( 27 | pipe( 28 | f, 29 | g, 30 | g, 31 | f 32 | )(1), 33 | 9 34 | ) 35 | equal( 36 | pipe( 37 | f, 38 | g, 39 | g, 40 | f, 41 | g, 42 | g, 43 | f 44 | )(1), 45 | 37 46 | ) 47 | }), 48 | ]) 49 | -------------------------------------------------------------------------------- /packages/functions/src/pipe/pipe.ts: -------------------------------------------------------------------------------- 1 | import { PipeFn } from './types' 2 | 3 | /** 4 | * Left-to-right function composition. 5 | * @name pipe(...fns: Array): (value: A) => B 6 | */ 7 | export const pipe: PipeFn = function pipe(...fns: Array) { 8 | return function(a: A): B { 9 | switch (fns.length) { 10 | case 1: 11 | return fns[0](a) 12 | case 2: 13 | return fns[1](fns[0](a)) 14 | case 3: 15 | return fns[2](fns[1](fns[0](a))) 16 | case 4: 17 | return fns[3](fns[2](fns[1](fns[0](a)))) 18 | case 5: 19 | return fns[4](fns[3](fns[2](fns[1](fns[0](a))))) 20 | default: 21 | return fns.reduce((accumulator: any, value: Function) => value(accumulator), a) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/functions/src/pipe/types.ts: -------------------------------------------------------------------------------- 1 | import { Arity1 } from '../types' 2 | 3 | export type PipeFn = { 4 | (f: Arity1): Arity1 5 | (f: Arity1, g: Arity1): Arity1 6 | (f: Arity1, g: Arity1, h: Arity1): Arity1 7 | (f: Arity1, g: Arity1, h: Arity1, i: Arity1): Arity1 8 | ( 9 | f: Arity1, 10 | g: Arity1, 11 | h: Arity1, 12 | i: Arity1, 13 | j: Arity1 14 | ): Arity1 15 | ( 16 | f: Arity1, 17 | g: Arity1, 18 | h: Arity1, 19 | i: Arity1, 20 | j: Arity1, 21 | k: Arity1 22 | ): Arity1 23 | ( 24 | f: Arity1, 25 | g: Arity1, 26 | h: Arity1, 27 | i: Arity1, 28 | j: Arity1, 29 | k: Arity1, 30 | l: Arity1 31 | ): Arity1 32 | ( 33 | f: Arity1, 34 | g: Arity1, 35 | h: Arity1, 36 | i: Arity1, 37 | j: Arity1, 38 | k: Arity1, 39 | l: Arity1, 40 | m: Arity1 41 | ): Arity1 42 | 43 | (...fns: Array>): Arity1 44 | } 45 | -------------------------------------------------------------------------------- /packages/future/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/future", 3 | "version": "5.0.0", 4 | "description": "Small future implemenatation", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only future", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:browser": "yarn test:unit -- --browser", 14 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 15 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 16 | }, 17 | "dependencies": { 18 | "@most/disposable": "1.2.1", 19 | "@most/types": "1.0.2", 20 | "@typed/functions": "3.0.0", 21 | "@typed/list": "3.0.0" 22 | }, 23 | "author": "Tylor Steinberger", 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /packages/future/src/all.test.ts: -------------------------------------------------------------------------------- 1 | import { Future, all, toPromise } from './' 2 | import { Test, describe, given, it } from '@typed/test' 3 | 4 | export const test: Test = describe(`all`, [ 5 | given(`an Like (Future a b)`, [ 6 | it(`returns Future a (List b)`, ({ equal }) => { 7 | const expected = [1, 2, 3] 8 | 9 | const resolvedFutures = expected.map(Future.of) 10 | 11 | return toPromise(all(resolvedFutures)).then(equal(expected)) 12 | }), 13 | ]), 14 | ]) 15 | -------------------------------------------------------------------------------- /packages/future/src/ap.ts: -------------------------------------------------------------------------------- 1 | import { Future } from './Future' 2 | import { chain } from './chain' 3 | import { curry2 } from '@typed/functions' 4 | import { map } from './map' 5 | 6 | /** 7 | * Applies the function resolved from a `Future` to the value resolved from a 8 | * second `Future`. 9 | * @name ap(fn: Future C>, value: Future): Future 10 | */ 11 | export const ap: FutureAp = curry2(__ap) 12 | 13 | function __ap(fn: Future C>, value: Future): Future { 14 | return chain(f => map(f, value), fn) 15 | } 16 | 17 | export type FutureAp = { 18 | (fn: Future C>, value: Future): Future 19 | (fn: Future C>): (value: Future) => Future 20 | } 21 | -------------------------------------------------------------------------------- /packages/future/src/chain.test.ts: -------------------------------------------------------------------------------- 1 | import { Future, chain, toPromise } from './' 2 | import { Test, describe, given, it } from '@typed/test' 3 | 4 | export const test: Test = describe(`chain`, [ 5 | given(`(b -> Future a c) and rejected Future a b`, [ 6 | it(`returns rejected Future and does not call function`, ({ rejects, equal }) => { 7 | let callCount = 0 8 | 9 | function f() { 10 | ++callCount 11 | 12 | return Future.of(1) 13 | } 14 | 15 | const expectedCallCount = 0 16 | 17 | return rejects(toPromise(chain(f, Future.reject(1)))).then(() => 18 | equal(expectedCallCount, callCount) 19 | ) 20 | }), 21 | ]), 22 | 23 | given(`(b -> Future a c) and resolved Future a b`, [ 24 | it(`returns result of (a -> Future a c)`, ({ equal, rejects }) => { 25 | let callCount = 0 26 | const f = () => { 27 | ++callCount 28 | 29 | return Future.reject(1) 30 | } 31 | 32 | const expectedCallCount = 1 33 | 34 | return rejects(toPromise(chain(f, Future.of(1)))).then(() => 35 | equal(expectedCallCount, callCount) 36 | ) 37 | }), 38 | ]), 39 | ]) 40 | -------------------------------------------------------------------------------- /packages/future/src/chain.ts: -------------------------------------------------------------------------------- 1 | import { Future } from './Future' 2 | import { curry2 } from '@typed/functions' 3 | 4 | /** 5 | * Returns a `Future` that is the result of calling `f` with the resolved 6 | * value of another future. Similar to `Promise.then`. 7 | * @name chain(f: (value: B) => Future, future: Future): Future 8 | */ 9 | export const chain: FutureChain = curry2(__chain) 10 | 11 | function __chain(f: (value: B) => Future, future: Future): Future { 12 | return Future.create((reject, resolve) => 13 | future.fork(reject, value => f(value).fork(reject, resolve)) 14 | ) 15 | } 16 | 17 | export type FutureChain = { 18 | (f: (value: B) => Future, future: Future): Future 19 | (f: (value: B) => Future): (future: Future) => Future 20 | } 21 | -------------------------------------------------------------------------------- /packages/future/src/chainLeft.ts: -------------------------------------------------------------------------------- 1 | import { Future } from './Future' 2 | import { curry2 } from '@typed/functions' 3 | 4 | /** 5 | * Returns a `Future` that is the result of calling `f` with the rejected 6 | * value of another future. Similar to `Promise.catch`. 7 | * @name chainLeft(f: (value: A) => Future, future: Future): Future 8 | */ 9 | export const chainLeft: FutureChainLeft = curry2(__chainLeft) 10 | 11 | function __chainLeft(f: (value: A) => Future, future: Future): Future { 12 | return Future.create((reject, resolve) => 13 | future.fork(value => f(value).fork(reject, resolve), resolve) 14 | ) 15 | } 16 | 17 | export type FutureChainLeft = { 18 | (f: (value: A) => Future, future: Future): Future 19 | (f: (value: A) => Future): (future: Future) => Future 20 | } 21 | -------------------------------------------------------------------------------- /packages/future/src/fork.ts: -------------------------------------------------------------------------------- 1 | import { Disposable, Future } from './Future' 2 | 3 | import { curry3 } from '@typed/functions' 4 | 5 | /** 6 | * Activates a future (side-effectful). 7 | * @name fork(left: (value: A) => any, right: (value: B) => any, future: Future): Disposable 8 | */ 9 | export const fork: ForkFn = curry3(forkFuture) 10 | 11 | function forkFuture( 12 | left: (value: A) => any, 13 | right: (value: B) => any, 14 | future: Future 15 | ): Disposable { 16 | return future.fork(left, right) 17 | } 18 | 19 | export interface ForkFn { 20 | (left: (value: A) => any, right: (value: B) => any, future: Future): Disposable 21 | (left: (value: A) => any): (right: (value: B) => any, future: Future) => Disposable 22 | (left: (value: A) => any, right: (value: B) => any): (future: Future) => Disposable 23 | (left: (value: A) => any): ( 24 | right: (value: B) => any 25 | ) => (future: Future) => Disposable 26 | } 27 | -------------------------------------------------------------------------------- /packages/future/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './all' 2 | export * from './ap' 3 | export * from './chain' 4 | export * from './chainLeft' 5 | export * from './fork' 6 | export * from './Future' 7 | export * from './map' 8 | export * from './mapLeft' 9 | export * from './sequence' 10 | export * from './toPromise' 11 | -------------------------------------------------------------------------------- /packages/future/src/map.ts: -------------------------------------------------------------------------------- 1 | import { Future } from './Future' 2 | import { chain } from './chain' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Maps the value of a Future. Similar to `Promise.then`. 7 | * @name map(f: (value: B) => C, future: Future): Future 8 | */ 9 | export const map: FutureMap = curry2(__map) 10 | 11 | function __map(f: (value: B) => C, future: Future): Future { 12 | return chain(b => Future.of(f(b)), future) 13 | } 14 | 15 | export type FutureMap = { 16 | (f: (value: B) => C, future: Future): Future 17 | (f: (value: B) => C): (future: Future) => Future 18 | } 19 | -------------------------------------------------------------------------------- /packages/future/src/mapLeft.ts: -------------------------------------------------------------------------------- 1 | import { Future } from './Future' 2 | import { chainLeft } from './chainLeft' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Returns a `Future` that is the result of calling `f` with the rejected 7 | * value of another future. Similar to `Promise.catch`. 8 | * @name mapLeft(f: (value: A) => C, future: Future): Future 9 | */ 10 | export const mapLeft: FutureMapLeft = curry2(__mapLeft) 11 | 12 | function __mapLeft(f: (value: A) => C, future: Future): Future { 13 | return chainLeft(value => Future.reject(f(value)), future) 14 | } 15 | 16 | export type FutureMapLeft = { 17 | (f: (value: A) => C, future: Future): Future 18 | (f: (value: A) => C): (future: Future) => Future 19 | } 20 | -------------------------------------------------------------------------------- /packages/future/src/sequence.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { Future } from './Future' 4 | import { sequence } from './sequence' 5 | import { toPromise } from './toPromise' 6 | 7 | export const test: Test = describe(`sequence`, [ 8 | given(`List (Future a b)`, [ 9 | it(`returns a Future`, ({ equal }) => { 10 | const expected = [1, 2, 3] 11 | 12 | const future = sequence(expected.map(Future.of)) 13 | 14 | return toPromise(future).then(equal(expected)) 15 | }), 16 | ]), 17 | ]) 18 | -------------------------------------------------------------------------------- /packages/future/src/sequence.ts: -------------------------------------------------------------------------------- 1 | import { Future } from './Future' 2 | import { chain } from './chain' 3 | import { map } from './map' 4 | 5 | /** 6 | * Creates a `Future` that will lazily fork each `Future` one after another. 7 | * Similar to `all` except that concurrency is always 1. 8 | * @name sequence(futures: ArrayLike>): Future> 9 | */ 10 | export function sequence(futures: ArrayLike>): Future> { 11 | let seed = Future.of, A>([]) 12 | 13 | for (let i = 0; i < futures.length; ++i) { 14 | const future = futures[i] 15 | 16 | seed = chain(values => map(value => values.concat(value), future), seed) 17 | } 18 | 19 | return seed 20 | } 21 | -------------------------------------------------------------------------------- /packages/future/src/toPromise.ts: -------------------------------------------------------------------------------- 1 | import { Future } from './Future' 2 | 3 | /** 4 | * Forks a `Future` into a Promise 5 | * @name toPromise(future: Future): Promise 6 | */ 7 | export function toPromise(future: Future>): Promise 8 | export function toPromise(future: Future): Promise 9 | export function toPromise(future: Future): Promise { 10 | return new Promise((resolve, reject) => future.fork(reject, resolve)) 11 | } 12 | -------------------------------------------------------------------------------- /packages/future/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@most/disposable@1.2.1": 6 | version "1.2.1" 7 | resolved "https://registry.yarnpkg.com/@most/disposable/-/disposable-1.2.1.tgz#c72d7de3d98a6662916285e4516c713a95b94fa3" 8 | integrity sha512-VQ5gGZd5VFqYyP3sfYmVbrb4MX7j7tqIQKpoATOz5loDXE/9Y/BU5SzD2pGhVtui62kUdK13UFSom4njtmQ1Lw== 9 | dependencies: 10 | "@most/prelude" "^1.7.2" 11 | "@most/types" "^1.0.1" 12 | 13 | "@most/prelude@^1.7.2": 14 | version "1.7.2" 15 | resolved "https://registry.yarnpkg.com/@most/prelude/-/prelude-1.7.2.tgz#be4ed406518d4c8c220e45c39fa7251365425b73" 16 | integrity sha512-GM5ec7+xpkuXiCMyzhyENgH/xZ8t0nAMDBY0QOsVVD6TrZYjJKUnW1eaI18HHX8W+COWMwWR9c0zoPiBp9+tUg== 17 | 18 | "@most/types@1.0.2": 19 | version "1.0.2" 20 | resolved "https://registry.yarnpkg.com/@most/types/-/types-1.0.2.tgz#a272c919a3dafe942bd02d63402f0548593af870" 21 | integrity sha512-ZVkDwaiuGVTXywADeJ3aUBsD41UURldIljlsSTMdiWfhVEOY7dsY8iq+9wFUjRZYjhnpn9tLiOHaqyjbxm1prg== 22 | 23 | "@most/types@^1.0.1": 24 | version "1.0.1" 25 | resolved "https://registry.yarnpkg.com/@most/types/-/types-1.0.1.tgz#8691580d1f3bf569b91d48f6688be9ad26dfc86c" 26 | integrity sha512-fCLkp1Yzp+Kamr00i1yG3GPhekOE150TSeoqUAy1cslmJ/a0+x7Y6GCwO26ke614sUbV9BdL0gTGCZJA4V1JUw== 27 | -------------------------------------------------------------------------------- /packages/lenses/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/lenses", 3 | "version": "3.0.0", 4 | "description": "Well-typed functional lenses", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only lenses", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:browser": "yarn test:unit -- --browser", 14 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 15 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 16 | }, 17 | "dependencies": { 18 | "@typed/functions": "3.0.0", 19 | "@typed/maybe": "7.0.0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/TylorS/typed.git" 24 | }, 25 | "keywords": [ 26 | "typed", 27 | "lenses", 28 | "functinal", 29 | "immutable" 30 | ], 31 | "author": "Tylor Steinberger ", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/TylorS/typed/issues" 35 | }, 36 | "homepage": "https://github.com/TylorS/typed#readme" 37 | } 38 | -------------------------------------------------------------------------------- /packages/lenses/src/composeLenses/composeLenses.ts: -------------------------------------------------------------------------------- 1 | import { ComposeLenses } from './types' 2 | import { Lens } from '../types' 3 | import { pipeLenses } from '../pipeLenses' 4 | 5 | /** 6 | * Right-to-left lens composition. 7 | * @name composeLenses(...lens: Array): Lens 8 | */ 9 | export const composeLenses: ComposeLenses = function( 10 | ...lenses: Array> 11 | ): Lens { 12 | return pipeLenses.apply(this, lenses.reverse()) 13 | } 14 | -------------------------------------------------------------------------------- /packages/lenses/src/composeLenses/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './composeLenses' 3 | -------------------------------------------------------------------------------- /packages/lenses/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './composeLenses' 3 | export * from './lens' 4 | export * from './pipeLenses' 5 | export * from './updateAt' 6 | export * from './view' 7 | -------------------------------------------------------------------------------- /packages/lenses/src/lens/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lens' 2 | -------------------------------------------------------------------------------- /packages/lenses/src/lens/lens.ts: -------------------------------------------------------------------------------- 1 | import { Maybe, fromJust, isNothing } from '@typed/maybe' 2 | 3 | import { Lens } from '../types' 4 | import { curry2 } from '@typed/functions' 5 | 6 | /** 7 | * Given a getter and a setter function, 8 | * it returns a Lens. 9 | * @name lens(getter: (a: A) => B | void, setter: (value: B, a: A) => A): Lens 10 | */ 11 | export const lens: LensFn = curry2(__lens) 12 | 13 | function __lens(getter: (a: A) => B | void, setter: (value: B, a: A) => A): Lens { 14 | function updateAt(f: (value: Maybe) => Maybe, a: A): A { 15 | const value = f(view(a)) 16 | 17 | if (isNothing(value)) return a 18 | 19 | return setter(fromJust(value), a) 20 | } 21 | 22 | function view(a: A): Maybe { 23 | return Maybe.of(getter(a)) 24 | } 25 | 26 | return { view, updateAt: curry2(updateAt) } 27 | } 28 | 29 | export type LensFn = { 30 | (getter: (a: A) => B, setter: (value: B, a: A) => A): Lens 31 | (getter: (a: A) => B): (setter: (value: B, a: A) => A) => Lens 32 | } 33 | -------------------------------------------------------------------------------- /packages/lenses/src/pipeLenses/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './pipeLenses' 3 | -------------------------------------------------------------------------------- /packages/lenses/src/pipeLenses/pipeLenses.ts: -------------------------------------------------------------------------------- 1 | import { Maybe, chain, fromJust, isNothing } from '@typed/maybe' 2 | 3 | import { Lens } from '../types' 4 | import { PipeLenses } from './types' 5 | import { curry2 } from '@typed/functions' 6 | 7 | /** 8 | * Left-to-right composition of Lenses. 9 | * @name pipeLenses(...lenses: Array>): Lens 10 | */ 11 | export const pipeLenses: PipeLenses = function pipeLenses( 12 | ...lenses: Array> 13 | ): Lens { 14 | return lenses.slice(1).reduce(__pipeLenses, lenses[0]) 15 | } 16 | 17 | function __pipeLenses(lensAB: Lens, lensBC: Lens): Lens { 18 | function view(obj: A): Maybe { 19 | return chain(b => lensBC.view(b), lensAB.view(obj)) 20 | } 21 | 22 | function updateAt(f: (value: Maybe) => Maybe, obj: A): A { 23 | const value = f(view(obj)) 24 | 25 | const nestedObject = lensAB.view(obj) 26 | 27 | if (isNothing(nestedObject)) return obj 28 | 29 | return lensAB.updateAt( 30 | () => Maybe.of(lensBC.updateAt(() => value, fromJust(nestedObject))), 31 | obj 32 | ) 33 | } 34 | 35 | return { view, updateAt: curry2(updateAt) } 36 | } 37 | -------------------------------------------------------------------------------- /packages/lenses/src/types.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from '@typed/maybe' 2 | 3 | /** 4 | * A common interface for Updating objects 5 | * @name Lens 6 | * @type 7 | */ 8 | export interface Lens { 9 | readonly view: (object: A) => Maybe 10 | readonly updateAt: LensUpdateAt 11 | } 12 | 13 | export type LensUpdateAt = { 14 | (f: (previousValue: Maybe) => Maybe, object: A): A 15 | (f: (previousValue: Maybe) => Maybe): (object: A) => A 16 | } 17 | -------------------------------------------------------------------------------- /packages/lenses/src/updateAt/index.ts: -------------------------------------------------------------------------------- 1 | import { Lens } from '../types' 2 | import { Maybe } from '@typed/maybe' 3 | import { curry3 } from '@typed/functions' 4 | 5 | /** 6 | * Uses a lenses to update a value contained in an object. 7 | * @name updateAt(lens: , f: (value: Maybe) => Maybe, obj: A): A 8 | */ 9 | export const updateAt: UpdateAt = curry3(function( 10 | lens: Lens, 11 | f: (value: Maybe) => Maybe, 12 | obj: A 13 | ): A { 14 | return lens.updateAt(f, obj) 15 | }) 16 | 17 | export type UpdateAt = { 18 | (lens: Lens, f: (value: Maybe) => Maybe, obj: A): A 19 | (lens: Lens, f: (value: Maybe) => Maybe): (obj: A) => A 20 | (lens: Lens): (f: (value: Maybe) => Maybe) => (obj: A) => A 21 | (lens: Lens): (f: (value: Maybe) => Maybe, obj: A) => A 22 | } 23 | -------------------------------------------------------------------------------- /packages/lenses/src/view/index.ts: -------------------------------------------------------------------------------- 1 | import { Lens } from '../types' 2 | import { Maybe } from '@typed/maybe' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Uses a lenses to view a value contained in an object. 7 | * @name view(lens: Lens, obj: A): Maybe 8 | */ 9 | export const view: View = curry2(function(lens: Lens, obj: A): Maybe { 10 | return lens.view(obj) 11 | }) 12 | 13 | export type View = { 14 | (lens: Lens, obj: A): Maybe 15 | (lens: Lens): (obj: A) => Maybe 16 | } 17 | -------------------------------------------------------------------------------- /packages/lenses/src/view/view.test.ts: -------------------------------------------------------------------------------- 1 | import { Just, fromJust, isJust } from '@typed/maybe' 2 | import { Test, describe, given, it } from '@typed/test' 3 | 4 | import { lens } from '../lens' 5 | import { view } from './' 6 | 7 | export const test: Test = describe(`view`, [ 8 | given(`a Lens and an object`, [ 9 | it(`calls Lens.view`, ({ ok, equal }) => { 10 | const value = 1 11 | const a = { a: value } 12 | const sut = lens(({ a }) => a, (a, o) => ({ ...o, a })) 13 | 14 | ok(isJust(view(sut, a))) 15 | equal(value, fromJust(view(sut, a) as Just)) 16 | }), 17 | ]), 18 | ]) 19 | -------------------------------------------------------------------------------- /packages/list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/list", 3 | "version": "3.0.0", 4 | "description": "Immutable List for TypeScript", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only list", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:browser": "yarn test:unit -- --browser", 14 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 15 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 16 | }, 17 | "dependencies": { 18 | "@typed/functions": "3.0.0", 19 | "@typed/lenses": "3.0.0", 20 | "@typed/logic": "3.0.0", 21 | "@typed/maybe": "7.0.0" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/TylorS/typed.git" 26 | }, 27 | "keywords": [ 28 | "typed", 29 | "list", 30 | "immutable", 31 | "typescript" 32 | ], 33 | "author": "Tylor Steinberger ", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/TylorS/typed/issues" 37 | }, 38 | "homepage": "https://github.com/TylorS/typed#readme" 39 | } 40 | -------------------------------------------------------------------------------- /packages/list/src/ap/index.ts: -------------------------------------------------------------------------------- 1 | import { Arity2, curry2 } from '@typed/functions' 2 | import { Index, List } from '../types' 3 | 4 | import { chain } from '../chain' 5 | import { map } from '../map' 6 | 7 | export const ap: ListAp = curry2(__ap) 8 | 9 | export type ListAp = { 10 | (fns: List>, values: List): Array 11 | (fns: List>): (values: List) => Array 12 | } 13 | 14 | function __ap(fns: List>, values: List): Array { 15 | return chain((fn: Arity2) => map(fn, values), fns) 16 | } 17 | -------------------------------------------------------------------------------- /packages/list/src/append/append.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { append } from './append' 4 | 5 | export const test: Test = describe(`append`, [ 6 | given(`a -> List a`, [ 7 | it(`returns List a`, ({ equal }) => { 8 | const value = 4 9 | const list = [1, 2, 3] 10 | const expected = [...list, value] 11 | 12 | equal(expected, append(value, list)) 13 | }), 14 | ]), 15 | ]) 16 | -------------------------------------------------------------------------------- /packages/list/src/append/append.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { curry2 } from '@typed/functions' 3 | import { length } from '../length' 4 | 5 | /** 6 | * Appends a value to the end of a list. 7 | * @name append(value: A, list: List): Array 8 | */ 9 | export const append: Append = curry2(__append) 10 | 11 | export type Append = { 12 | (value: A, list: List): Array 13 | (value: A): (list: List) => Array 14 | } 15 | 16 | function __append(value: A, list: List): Array { 17 | const itemCount = length(list) 18 | const newList = Array(itemCount + 1) 19 | 20 | for (let i = 0; i < itemCount; ++i) newList[i] = list[i] 21 | 22 | newList[itemCount] = value 23 | 24 | return newList 25 | } 26 | -------------------------------------------------------------------------------- /packages/list/src/append/index.ts: -------------------------------------------------------------------------------- 1 | export * from './append' 2 | -------------------------------------------------------------------------------- /packages/list/src/arrayFrom/arrayFrom.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | /** 4 | * Converts any `Iterable`, `Iterator` or `ArrayLike` to an `Array`. 5 | * @name arrayFrom(iterable: Iterable | Iterator | List): Array 6 | */ 7 | export function arrayFrom(iterable: Iterable | Iterator | List): Array { 8 | if (isIterator(iterable)) return Array.from(toIterable(iterable)) 9 | 10 | return Array.from(iterable as Iterable | List) 11 | } 12 | 13 | function isIterator(x: any): x is Iterator { 14 | return x && typeof (x as Iterator).next === 'function' 15 | } 16 | 17 | function toIterable(iterator: Iterator): Iterable { 18 | return { 19 | [Symbol.iterator]() { 20 | return iterator 21 | }, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/list/src/arrayFrom/index.ts: -------------------------------------------------------------------------------- 1 | export * from './arrayFrom' 2 | -------------------------------------------------------------------------------- /packages/list/src/ascend/ascend.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { ascend } from './' 4 | 5 | export const test: Test = describe(`ascend`, [ 6 | given(`(a -> b), a and a`, [ 7 | it(`returns a ComparisionNumber`, ({ equal }) => { 8 | const a = { a: 1 } 9 | const b = { a: 2 } 10 | const byA = (x: typeof a): number => x.a 11 | 12 | equal(ascend(byA, a, b), -1) 13 | equal(ascend(byA, b, a), 1) 14 | equal(ascend(byA, a, a), 0) 15 | }), 16 | ]), 17 | ]) 18 | -------------------------------------------------------------------------------- /packages/list/src/ascend/index.ts: -------------------------------------------------------------------------------- 1 | import { ComparisonNumbers, curry3 } from '@typed/functions' 2 | 3 | /** 4 | * Makes an ascending comparator function out of a function that returns a 5 | * value that can be compared with < and >. 6 | * @name ascend(f: (a: A) => B, a: A, b: A): ComparisonNumbers 7 | */ 8 | export const ascend: AscendArity3 = curry3(function ascend( 9 | f: (a: A) => B, 10 | a: A, 11 | b: A 12 | ): ComparisonNumbers { 13 | const aa = f(a) 14 | const bb = f(b) 15 | 16 | if (aa < bb) return -1 17 | 18 | if (aa > bb) return 1 19 | 20 | return 0 21 | }) 22 | 23 | export type AscendArity3 = { 24 | (f: (a: A) => B, a: A, b: A): ComparisonNumbers 25 | (f: (a: A) => B): AscendArity2 26 | (f: (a: A) => B, a: A): AscendArity1 27 | } 28 | 29 | export type AscendArity2 = { 30 | (a: A, b: A): ComparisonNumbers 31 | (a: A): AscendArity1 32 | } 33 | 34 | export type AscendArity1 = (b: A) => ComparisonNumbers 35 | -------------------------------------------------------------------------------- /packages/list/src/chain/index.ts: -------------------------------------------------------------------------------- 1 | import { Arity2, curry2 } from '@typed/functions' 2 | import { Index, List } from '../types' 3 | 4 | import { flatten } from '../flatten' 5 | import { map } from '../map' 6 | 7 | export const chain: ListChain = curry2(__chain) 8 | 9 | export type ListChain = { 10 | (f: Arity2>, list: List): Array 11 | (f: Arity2>): (list: List) => Array 12 | } 13 | 14 | function __chain(f: Arity2>, list: List): Array { 15 | return flatten(map(f, list)) 16 | } 17 | -------------------------------------------------------------------------------- /packages/list/src/concat/concat.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, it } from '@typed/test' 2 | 3 | import { concat } from './concat' 4 | 5 | export const test: Test = describe(`concat`, [ 6 | it('concatenates two lists', ({ equal }) => { 7 | equal(concat([1, 2], [3, 4]), [1, 2, 3, 4]) 8 | }), 9 | 10 | it('concatenates two strings', ({ equal }) => { 11 | equal(concat('foo', 'bar'), ['f', 'o', 'o', 'b', 'a', 'r']) 12 | }), 13 | ]) 14 | -------------------------------------------------------------------------------- /packages/list/src/concat/concat.ts: -------------------------------------------------------------------------------- 1 | import { Concat } from './types' 2 | import { List } from '../types' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Returns the result of concatenating the given lists or strings. 7 | * @name concat(list1: List, list2: List): Array 8 | */ 9 | export const concat: Concat = curry2(function(list1: List, list2: List): Array { 10 | const length1 = list1.length 11 | const length2 = list2.length 12 | const newList = Array(length1 + length2) 13 | 14 | for (let i = 0; i < length1; ++i) newList[i] = list1[i] 15 | 16 | for (let i = 0; i < length2; ++i) newList[i + length1] = list2[i] 17 | 18 | return newList 19 | }) 20 | -------------------------------------------------------------------------------- /packages/list/src/concat/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './concat' 3 | -------------------------------------------------------------------------------- /packages/list/src/concat/types.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | export type Concat = { 4 | (list1: List, list2: List): Array 5 | (list1: List): (list2: List) => Array 6 | } 7 | -------------------------------------------------------------------------------- /packages/list/src/contains/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { curry2 } from '@typed/functions' 3 | import { fromMaybe } from '@typed/maybe' 4 | import { indexOf } from '../indexOf' 5 | import { map } from '@typed/maybe' 6 | 7 | /** 8 | * Returns true if a list contains a value, false otherwise. 9 | * @name contains(value: A, list: List): boolean 10 | */ 11 | export const contains: Contains = curry2(__contains) 12 | 13 | export type Contains = { 14 | (value: A, list: List): boolean 15 | (value: A): (list: List) => boolean 16 | } 17 | 18 | function __contains(value: A, list: List): boolean { 19 | return fromMaybe(false, map(() => true, indexOf(value, list))) 20 | } 21 | -------------------------------------------------------------------------------- /packages/list/src/descend/index.ts: -------------------------------------------------------------------------------- 1 | import { ComparisonNumbers, curry3 } from '@typed/functions' 2 | 3 | /** 4 | * Makes a descending comparator function out of a function that returns a 5 | * value that can be compared with < and >. 6 | * @name descend(f: (a: A) => B, a: A, b: A): ComparisonNumbers 7 | */ 8 | export const descend: Descend = curry3(function ascend( 9 | f: (a: A) => B, 10 | a: A, 11 | b: A 12 | ): ComparisonNumbers { 13 | const aa = f(a) 14 | const bb = f(b) 15 | 16 | if (aa < bb) return 1 17 | 18 | if (aa > bb) return -1 19 | 20 | return 0 21 | }) 22 | 23 | export type Descend = { 24 | (f: (a: A) => B, a: A, b: A): ComparisonNumbers 25 | (f: (a: A) => B, a: A): (b: A) => ComparisonNumbers 26 | (f: (a: A) => B): { 27 | (a: A): (b: A) => ComparisonNumbers 28 | (a: A, b: A): ComparisonNumbers 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/list/src/drop/drop.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { Nothing } from '@typed/maybe' 3 | import { curry2 } from '@typed/functions' 4 | import { slice } from '../slice' 5 | 6 | /** 7 | * Drops the first `n` items from a `List`. 8 | * @name drop(quantity: number, list: List): Array 9 | */ 10 | export const drop: Drop = curry2(__drop) 11 | 12 | export type Drop = { 13 | (quanity: number, list: List): Array 14 | (quanity: number): (list: List) => Array 15 | } 16 | 17 | function __drop(quanity: number, list: List): Array { 18 | return slice(quanity, Nothing, list) 19 | } 20 | -------------------------------------------------------------------------------- /packages/list/src/drop/index.ts: -------------------------------------------------------------------------------- 1 | export * from './drop' 2 | -------------------------------------------------------------------------------- /packages/list/src/dropLast/dropLast.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { Maybe } from '@typed/maybe' 3 | import { curry2 } from '@typed/functions' 4 | import { slice } from '../slice' 5 | 6 | /** 7 | * Drops `n` number of items from the end of a `List`. 8 | * @name dropLast(quantity: number, list: List): Array 9 | */ 10 | export const dropLast: DropLast = curry2(__dropLast) 11 | 12 | export type DropLast = { 13 | (quanity: number, list: List): Array 14 | (quanity: number): (list: List) => Array 15 | } 16 | 17 | function __dropLast(quanity: number, list: List): Array { 18 | return slice(0, Maybe.of(list.length - quanity), list) 19 | } 20 | -------------------------------------------------------------------------------- /packages/list/src/dropLast/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dropLast' 2 | -------------------------------------------------------------------------------- /packages/list/src/endsWith/endsWith.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { endsWith } from './endsWith' 4 | 5 | export const test: Test = describe(`endsWith`, [ 6 | given(`an search string and a string ending in search`, [ 7 | it(`returns true`, ({ ok }) => { 8 | const search = 'lo' 9 | const str = 'hello' 10 | 11 | ok(endsWith(search, str)) 12 | }), 13 | ]), 14 | 15 | given(`an search string and a string not ending in search`, [ 16 | it(`returns false`, ({ notOk }) => { 17 | const search = 'no' 18 | const str = 'hello' 19 | 20 | notOk(endsWith(search, str)) 21 | }), 22 | ]), 23 | ]) 24 | -------------------------------------------------------------------------------- /packages/list/src/endsWith/endsWith.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { curry2 } from '@typed/functions' 3 | import { length } from '../length' 4 | 5 | /** 6 | * Returns true if a list ends with a certain search. 7 | * @name endsWith(expected: List, list: List): boolean 8 | */ 9 | export const endsWith: EndsWith = curry2(__endsWith) 10 | 11 | export type EndsWith = { 12 | (expected: List, list: List): boolean 13 | (expected: List): (list: List) => boolean 14 | } 15 | 16 | function __endsWith(expected: List, list: List): boolean { 17 | const itemCount = length(list) 18 | const expectedCount = length(expected) 19 | const startingIndex = itemCount - expectedCount 20 | 21 | if (startingIndex < 0) return false 22 | 23 | for (let i = 0; i < expectedCount; ++i) if (expected[i] !== list[startingIndex + i]) return false 24 | 25 | return true 26 | } 27 | -------------------------------------------------------------------------------- /packages/list/src/endsWith/index.ts: -------------------------------------------------------------------------------- 1 | export * from './endsWith' 2 | -------------------------------------------------------------------------------- /packages/list/src/filter/filter.ts: -------------------------------------------------------------------------------- 1 | import { Filter } from './types' 2 | import { List } from '../types' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Takes a predicate and a "filterable", and returns a new filterable of the 7 | * same type containing the members of the given filterable which satisfy the 8 | * given predicate. 9 | * Dispatches to the filter method of the second argument, if present. 10 | * @name filter(predicate: (value: A, index: number) => boolean, list: List): Array 11 | */ 12 | export const filter: Filter = curry2(function filter( 13 | predicate: (a: A, index: number) => boolean, 14 | list: List 15 | ): Array { 16 | const length = list.length 17 | const newList = [] 18 | 19 | for (let i = 0; i < length; ++i) if (predicate(list[i], i)) newList.push(list[i]) 20 | 21 | return newList 22 | }) 23 | -------------------------------------------------------------------------------- /packages/list/src/filter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './filter' 3 | -------------------------------------------------------------------------------- /packages/list/src/filter/types.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | export type Filter = { 4 | (f: (a: A, index: number) => boolean, list: List): Array 5 | (f: (a: A, index: number) => a is B, list: List): Array 6 | 7 | (f: (a: A, index: number) => boolean): (list: List) => Array 8 | (f: (a: A, index: number) => a is B): (list: List) => Array 9 | } 10 | -------------------------------------------------------------------------------- /packages/list/src/find/index.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | import { Maybe, map } from '@typed/maybe' 3 | 4 | import { curry2 } from '@typed/functions' 5 | import { findIndex } from '../findIndex' 6 | 7 | /** 8 | * Find the value contained in a list. 9 | * @name find(predicate: Predicate, list: List): Maybe 10 | */ 11 | export const find: Find = curry2(__find) as Find 12 | 13 | export type Find = { 14 | (predicate: (value: any) => boolean, list: List): Maybe> 15 | (predicate: (value: any) => boolean): (list: List) => Maybe> 16 | } 17 | 18 | const prop = (list: List) => (index: Index): A => list[index] 19 | 20 | function __find(predicate: (value: any) => boolean, list: List): Maybe { 21 | return map(prop(list), findIndex(predicate, list)) 22 | } 23 | -------------------------------------------------------------------------------- /packages/list/src/findIndex/findIndex.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | import { fromJust, isJust, isNothing } from '@typed/maybe' 3 | 4 | import { findIndex } from './findIndex' 5 | 6 | export const test: Test = describe(`findIndex`, [ 7 | given(`(a -> bool) and List a, where predicate returns true`, [ 8 | it(`returns Just Index`, ({ equal }) => { 9 | const list = [1, 2, 3] 10 | const predicate = (x: number) => x === 3 11 | const expectedIndex = 2 12 | 13 | const actual = findIndex(predicate, list) 14 | 15 | if (isJust(actual)) equal(expectedIndex, fromJust(actual)) 16 | }), 17 | ]), 18 | 19 | given(`(a -> bool) and List a, where predicate returns false`, [ 20 | it(`returns Nothing`, ({ ok }) => { 21 | const list = [1, 2, 3] 22 | const predicate = () => false 23 | 24 | const actual = findIndex(predicate, list) 25 | 26 | ok(isNothing(actual)) 27 | }), 28 | ]), 29 | ]) 30 | -------------------------------------------------------------------------------- /packages/list/src/findIndex/findIndex.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | import { Maybe, Nothing } from '@typed/maybe' 3 | 4 | import { curry2 } from '@typed/functions' 5 | import { length } from '../length' 6 | 7 | /** 8 | * Find the index of a value in a list. 9 | * @name findIndex(predicate: Predicate, list: List): Maybe 10 | */ 11 | export const findIndex: FindIndex = curry2(__findIndex) 12 | 13 | export type FindIndex = { 14 | (predicate: (value: any) => boolean, list: List): Maybe 15 | (predicate: (value: any) => boolean): (list: List) => Maybe 16 | } 17 | 18 | function __findIndex(predicate: (value: A) => boolean, list: List): Maybe { 19 | const itemCount = length(list) 20 | 21 | for (let i = 0; i < itemCount; ++i) if (predicate(list[i])) return Maybe.of(i) 22 | 23 | return Nothing 24 | } 25 | -------------------------------------------------------------------------------- /packages/list/src/findIndex/index.ts: -------------------------------------------------------------------------------- 1 | export * from './findIndex' 2 | -------------------------------------------------------------------------------- /packages/list/src/findLast/index.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | import { Maybe, map } from '@typed/maybe' 3 | 4 | import { curry2 } from '@typed/functions' 5 | import { findLastIndex } from '../findLastIndex' 6 | 7 | /** 8 | * Find the last value contained in a list. 9 | * @name findLast(predicate: Predicate, list: List): Maybe 10 | */ 11 | export const findLast: FindLast = curry2(__findLast) 12 | 13 | export type FindLast = { 14 | (predicate: (value: any) => boolean, list: List): Maybe 15 | (predicate: (value: any) => boolean): (list: List) => Maybe 16 | } 17 | 18 | const propFlipped = (list: List) => (index: Index): A => list[index] 19 | 20 | function __findLast(predicate: (value: any) => boolean, list: List): Maybe { 21 | return map(propFlipped(list), findLastIndex(predicate, list)) 22 | } 23 | -------------------------------------------------------------------------------- /packages/list/src/findLastIndex/findLastIndex.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | import { Maybe, Nothing } from '@typed/maybe' 3 | 4 | import { curry2 } from '@typed/functions' 5 | import { length } from '../length' 6 | 7 | /** 8 | * Find the last index of a value in a list. 9 | * @name findLastIndex(predicate: Predicate, list: List): Maybe 10 | */ 11 | export const findLastIndex: FindLastIndex = curry2(__findLastIndex) 12 | 13 | export type FindLastIndex = { 14 | (predicate: (value: A) => boolean, list: List): Maybe 15 | (predicate: (value: A) => boolean): (list: List) => Maybe 16 | } 17 | 18 | function __findLastIndex(predicate: (value: A) => boolean, list: List): Maybe { 19 | const itemCount = length(list) 20 | 21 | for (let i = itemCount - 1; i >= 0; --i) if (predicate(list[i])) return Maybe.of(i) 22 | 23 | return Nothing 24 | } 25 | -------------------------------------------------------------------------------- /packages/list/src/findLastIndex/index.ts: -------------------------------------------------------------------------------- 1 | export * from './findLastIndex' 2 | -------------------------------------------------------------------------------- /packages/list/src/flatten/flatten.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { flatten } from './flatten' 4 | 5 | export const test: Test = describe(`flatten`, [ 6 | given(`an array of arrays`, [ 7 | it(`returns an unnested array`, ({ equal }) => { 8 | const arr = [[1, 2], [3, 4], [5, 6], [[[7, 8]]]] 9 | 10 | equal([1, 2, 3, 4, 5, 6, 7, 8], flatten(arr)) 11 | }), 12 | ]), 13 | ]) 14 | -------------------------------------------------------------------------------- /packages/list/src/flatten/flatten.ts: -------------------------------------------------------------------------------- 1 | import { Flatten } from './types' 2 | import { List } from '../types' 3 | import { arrayFrom } from '../arrayFrom' 4 | import { isList } from '../isList' 5 | import { reduce } from '../reduce' 6 | 7 | /** 8 | * Returns a new list by pulling every item out of it (and all its sub-arrays) 9 | * and putting them in a new array, depth-first. 10 | */ 11 | export const flatten: Flatten = function flatten(list: List): Array { 12 | return reduce, Array>(flattenReducer, [], list) 13 | } 14 | 15 | function flattenReducer(acc: Array, value: A | List): Array { 16 | if (isList(value)) acc.push(...arrayFrom(flatten(value))) 17 | else acc.push(value) 18 | 19 | return acc 20 | } 21 | -------------------------------------------------------------------------------- /packages/list/src/flatten/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './flatten' 3 | -------------------------------------------------------------------------------- /packages/list/src/flatten/types.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | export type Flatten = { 4 | (list: List>): Array 5 | (list: List | List>>): Array 6 | (list: List | List> | List>>>): Array 7 | ( 8 | list: List | List> | List>> | List>>>> 9 | ): Array 10 | (list: List>): Array 11 | (list: List>>): Array 12 | (list: List>>>): Array 13 | (list: List>>>>): Array 14 | (list: List): Array 15 | } 16 | -------------------------------------------------------------------------------- /packages/list/src/forEach/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { arrayFrom } from '../arrayFrom' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Applies a function to each item in a `List`, returning the list after. 7 | * @name forEach(f: (value: A, index: number) => any, list: List): Array 8 | */ 9 | export const forEach: ForEach = curry2(__forEach) 10 | 11 | export type ForEach = { 12 | (f: (value: A, index: number) => any, list: List): Array 13 | (f: (value: A, index: number) => any): (list: List) => Array 14 | } 15 | 16 | function __forEach(f: (value: A, index: number) => any, list: List): Array { 17 | for (let i = 0; i < list.length; ++i) f(list[i], i) 18 | 19 | return arrayFrom(list) 20 | } 21 | -------------------------------------------------------------------------------- /packages/list/src/groupBy/groupBy.ts: -------------------------------------------------------------------------------- 1 | import { GroupBy } from './types' 2 | import { List } from '../types' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Groups a list by keys returned by applying the provided function to each 7 | * item. 8 | * @name groupBy(f: Arity1, list: List): Readonly>> 9 | */ 10 | export const groupBy: GroupBy = curry2(__groupBy) 11 | 12 | function __groupBy( 13 | f: (value: A) => Keys, 14 | list: List 15 | ): Readonly>> { 16 | const itemCount = list.length 17 | const strMap = {} as Record> 18 | 19 | for (let i = 0; i < itemCount; ++i) { 20 | const value = list[i] 21 | const key = f(value) 22 | 23 | if (strMap[key]) strMap[key].push(value) 24 | else strMap[key] = [value] 25 | } 26 | 27 | return strMap 28 | } 29 | -------------------------------------------------------------------------------- /packages/list/src/groupBy/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './groupBy' 3 | -------------------------------------------------------------------------------- /packages/list/src/groupBy/types.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | export type GroupBy = { 4 | (f: (value: Value) => Keys, list: List): Readonly< 5 | Record 6 | > 7 | (f: (value: Value) => Keys): ( 8 | list: List 9 | ) => Readonly> 10 | } 11 | -------------------------------------------------------------------------------- /packages/list/src/head/index.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | 3 | import { Maybe } from '@typed/maybe' 4 | import { lensIndex } from '../lensIndex' 5 | 6 | const HEAD_INDEX: Index = 0 7 | 8 | /** 9 | * Returns the first item of a list. 10 | * @name head(list: List): Maybe 11 | */ 12 | export function head(list: List): Maybe { 13 | const { view } = lensIndex(HEAD_INDEX) 14 | 15 | return view(list) 16 | } 17 | -------------------------------------------------------------------------------- /packages/list/src/includes/includes.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { includes } from './includes' 4 | 5 | export const test: Test = describe(`includes`, [ 6 | given(`a search string an a string that includes the search`, [ 7 | it(`returns true`, ({ ok }) => { 8 | const search = 'ell' 9 | const str = 'hello' 10 | 11 | ok(includes(search, str)) 12 | }), 13 | ]), 14 | 15 | given(`a search string an a string that doesn't include the search`, [ 16 | it(`returns false`, ({ notOk }) => { 17 | const search = 'no' 18 | const str = 'hello' 19 | 20 | notOk(includes(search, str)) 21 | }), 22 | ]), 23 | 24 | given(`two identical arrays`, [ 25 | it(`returns true`, ({ ok }) => { 26 | const a = [0, 1, 2] 27 | const b = [0, 1, 2] 28 | 29 | ok(includes(a, b)) 30 | }), 31 | ]), 32 | ]) 33 | -------------------------------------------------------------------------------- /packages/list/src/includes/includes.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | 3 | import { curry2 } from '@typed/functions' 4 | import { length } from '../length' 5 | 6 | /** 7 | * Returns true if a list contains a search value. 8 | * @name include(search: List, list: List): boolean 9 | */ 10 | export const includes: Includes = curry2(__includes) 11 | 12 | export type Includes = { 13 | (search: List, list: List): boolean 14 | (search: List): (list: List) => boolean 15 | } 16 | 17 | function __includes(search: List, list: List): boolean { 18 | const indexes = __indexesOf(search[0], list) 19 | 20 | return indexes.some(__includesFromIndex(search, list)) 21 | } 22 | 23 | function __includesFromIndex(search: List, list: List) { 24 | const searchCount = length(search) 25 | 26 | return function(index: Index): boolean { 27 | for (let i = 1; i < searchCount; ++i) if (search[i] !== list[index + i]) return false 28 | 29 | return true 30 | } 31 | } 32 | 33 | function __indexesOf(value: A, list: List): Array { 34 | const itemCount = length(list) 35 | const indexes = [] 36 | 37 | for (let i = 0; i < itemCount; ++i) if (list[i] === value) indexes.push(i) 38 | 39 | return indexes 40 | } 41 | -------------------------------------------------------------------------------- /packages/list/src/includes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './includes' 2 | -------------------------------------------------------------------------------- /packages/list/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | 3 | export * from './ap' 4 | export * from './append' 5 | export * from './arrayFrom' 6 | export * from './ascend' 7 | export * from './chain' 8 | export * from './concat' 9 | export * from './contains' 10 | export * from './descend' 11 | export * from './drop' 12 | export * from './dropLast' 13 | export * from './endsWith' 14 | export * from './filter' 15 | export * from './find' 16 | export * from './findIndex' 17 | export * from './findLast' 18 | export * from './findLastIndex' 19 | export * from './flatten' 20 | export * from './forEach' 21 | export * from './groupBy' 22 | export * from './head' 23 | export * from './includes' 24 | export * from './indexOf' 25 | export * from './insert' 26 | export * from './isList' 27 | export * from './join' 28 | export * from './last' 29 | export * from './lastIndexOf' 30 | export * from './length' 31 | export * from './lensIndex' 32 | export * from './map' 33 | export * from './move' 34 | export * from './prepend' 35 | export * from './range' 36 | export * from './reduce' 37 | export * from './reduceRight' 38 | export * from './remove' 39 | export * from './reverse' 40 | export * from './slice' 41 | export * from './sort' 42 | export * from './splitAt' 43 | export * from './splitEvery' 44 | export * from './startsWith' 45 | export * from './take' 46 | export * from './takeLast' 47 | export * from './uniq' 48 | export * from './update' 49 | export * from './without' 50 | -------------------------------------------------------------------------------- /packages/list/src/indexOf/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './indexOf' 3 | -------------------------------------------------------------------------------- /packages/list/src/indexOf/indexOf.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | 3 | import { IndexOf } from './types' 4 | import { Maybe } from '@typed/maybe' 5 | import { curry2 } from '@typed/functions' 6 | import { equals } from '@typed/logic' 7 | import { findIndex } from '../findIndex' 8 | 9 | /** 10 | * Returns the index of a value in a `List`. 11 | * @name indexOf(value: A, list: List): Maybe 12 | */ 13 | export const indexOf: IndexOf = curry2(__indexOf) 14 | 15 | function __indexOf(value: A, list: List): Maybe { 16 | return findIndex(equals(value), list) 17 | } 18 | -------------------------------------------------------------------------------- /packages/list/src/indexOf/types.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | 3 | import { Maybe } from '@typed/maybe' 4 | 5 | export type IndexOf = { 6 | (value: A, list: List): Maybe 7 | (value: A): (list: List) => Maybe 8 | } 9 | -------------------------------------------------------------------------------- /packages/list/src/insert/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './insert' 3 | -------------------------------------------------------------------------------- /packages/list/src/insert/insert.ts: -------------------------------------------------------------------------------- 1 | import { InsertArity3 } from './types' 2 | import { List } from '../types' 3 | import { arrayFrom } from '../arrayFrom' 4 | import { curry3 } from '@typed/functions' 5 | 6 | /** 7 | * Inserts a value into a `List`. at a given index. 8 | * @name insert(index: number, value: A, list: List): Array 9 | */ 10 | export const insert: InsertArity3 = curry3(function insert( 11 | index: number, 12 | value: A, 13 | list: List 14 | ): Array { 15 | const length = list.length 16 | 17 | if (index < 0) return arrayFrom(list) 18 | 19 | if (length === 0) return [value] 20 | 21 | const newList = [] 22 | let i = 0 23 | 24 | for (; i < index; ++i) newList[i] = list[i] 25 | 26 | newList[i++] = value 27 | 28 | for (; i <= length; ++i) newList[i] = list[i - 1] 29 | 30 | return newList 31 | }) 32 | -------------------------------------------------------------------------------- /packages/list/src/insert/types.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | export type InsertArity3 = { 4 | (index: number, value: A, list: List): Array 5 | (index: number, value: A): InsertArity1 6 | (index: number): InsertArity2 7 | } 8 | 9 | export type InsertArity2 = { 10 | (value: A, list: List): Array 11 | (value: A): InsertArity1 12 | } 13 | 14 | export type InsertArity1 = { 15 | (list: List): Array 16 | } 17 | -------------------------------------------------------------------------------- /packages/list/src/isList/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | /** 4 | * Returns true if a value is a `List`. 5 | * @name isList(x: any): x is List 6 | */ 7 | export function isList(x: any): x is List { 8 | if (Array.isArray(x)) return true 9 | if (!x || typeof x !== 'object' || typeof x === 'string') return false 10 | if (x.nodeType === 1) return !!x.length 11 | if (x.length === 0) return true 12 | if (x.length > 0) return x.hasOwnProperty(0) && x.hasOwnProperty(x.length - 1) 13 | return false 14 | } 15 | -------------------------------------------------------------------------------- /packages/list/src/join/index.ts: -------------------------------------------------------------------------------- 1 | export * from './join' 2 | -------------------------------------------------------------------------------- /packages/list/src/join/join.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { join } from './join' 4 | 5 | export const test: Test = describe(`join`, [ 6 | given(`a separator and an empty list`, [ 7 | it(`returns empty string`, ({ equal }) => { 8 | equal('', join('::', [])) 9 | }), 10 | ]), 11 | 12 | given(`a separator and a list containing 1 item`, [ 13 | it(`returns item contained in list`, ({ equal }) => { 14 | const value = 'hello' 15 | const list = [value] 16 | 17 | equal(value, join('::', list)) 18 | }), 19 | ]), 20 | 21 | given(`a separator and a list of 2+ items`, [ 22 | it(`returns each item joined by the separator`, ({ equal }) => { 23 | const one = 'one' 24 | const two = 'two' 25 | const three = 'three' 26 | const separator = '::' 27 | 28 | const expected = `${one}${separator}${two}${separator}${three}` 29 | 30 | equal(expected, join(separator, [one, two, three])) 31 | }), 32 | ]), 33 | ]) 34 | -------------------------------------------------------------------------------- /packages/list/src/join/join.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { curry2 } from '@typed/functions' 3 | 4 | /** 5 | * Takes a `List` and concatenates them via with a defined 6 | * separator. 7 | * @name join(separator: string, list: List): string 8 | */ 9 | export const join: Join = curry2(__join) 10 | 11 | export type Join = { 12 | (separator: string, list: List): string 13 | (separator: string): (list: List) => string 14 | } 15 | 16 | function __join(separator: string, list: List): string { 17 | const itemCount = list.length 18 | 19 | let str = '' 20 | 21 | if (itemCount === 0) return str 22 | 23 | str += list[0] 24 | 25 | if (itemCount === 1) return str 26 | 27 | for (let i = 1; i < itemCount; ++i) str += separator + list[i] 28 | 29 | return str 30 | } 31 | -------------------------------------------------------------------------------- /packages/list/src/last/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../' 2 | import { Maybe } from '@typed/maybe' 3 | import { lensIndex } from '../lensIndex' 4 | 5 | /** 6 | * Returns the last item in a `List`. 7 | * @name last(list: List): Maybe 8 | */ 9 | export function last(list: List): Maybe { 10 | const index = list.length - 1 11 | const { view } = lensIndex(index) 12 | 13 | return view(list) 14 | } 15 | -------------------------------------------------------------------------------- /packages/list/src/lastIndexOf/index.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | 3 | import { Maybe } from '@typed/maybe' 4 | import { curry2 } from '@typed/functions' 5 | import { equals } from '@typed/logic' 6 | import { findLastIndex } from '../findLastIndex' 7 | 8 | /** 9 | * Finds the last index of a value in a `List`. 10 | * @name lastIndexOf(value: A, list: List): Maybe 11 | */ 12 | export const lastIndexOf: LastIndexOf = curry2(__lastIndexOf) 13 | 14 | export type LastIndexOf = { 15 | (value: A, list: List): Maybe 16 | (value: A): (list: List) => Maybe 17 | } 18 | 19 | function __lastIndexOf(value: A, list: List): Maybe { 20 | return findLastIndex(equals(value), list) 21 | } 22 | -------------------------------------------------------------------------------- /packages/list/src/length/index.ts: -------------------------------------------------------------------------------- 1 | export function length({ length }: { length: A }): A { 2 | return length 3 | } 4 | -------------------------------------------------------------------------------- /packages/list/src/lensIndex/index.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | import { Lens, lens } from '@typed/lenses' 3 | 4 | import { update } from '../update' 5 | 6 | /** 7 | * Creates an index for a List. 8 | * @name lensIndex(index: number): Lens, A> 9 | */ 10 | export function lensIndex(index: Index): Lens, A> { 11 | return lens((array: List): A => array[index], (value, list) => update(index, value, list)) 12 | } 13 | -------------------------------------------------------------------------------- /packages/list/src/map/index.ts: -------------------------------------------------------------------------------- 1 | import { Arity2, curry2 } from '@typed/functions' 2 | import { Index, List } from '../types' 3 | 4 | export const map: Map = curry2(__map) 5 | 6 | export type Map = { 7 | (f: Arity2, list: List): Array 8 | (f: Arity2): (list: List) => Array 9 | } 10 | 11 | function __map(f: Arity2, list: List): Array { 12 | const itemCount = list.length 13 | const newList = Array(itemCount) 14 | 15 | for (let i = 0; i < itemCount; ++i) newList[i] = f(list[i], i) 16 | 17 | return newList 18 | } 19 | -------------------------------------------------------------------------------- /packages/list/src/move/index.ts: -------------------------------------------------------------------------------- 1 | export * from './move' 2 | -------------------------------------------------------------------------------- /packages/list/src/move/move.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { move } from './move' 4 | import { range } from '../range' 5 | 6 | export const test: Test = describe(`move`, [ 7 | given(`fromIndex, a toIndex, and an array`, [ 8 | it(`returns a new array with fromIndex at toIndex`, ({ equal }) => { 9 | equal(move(1, 3, range(1, 6)), [1, 3, 4, 2, 5]) 10 | equal(move(3, 1, range(1, 6)), [1, 4, 2, 3, 5]) 11 | equal(move(1, 1, range(1, 6)), range(1, 6)) 12 | }), 13 | ]), 14 | ]) 15 | -------------------------------------------------------------------------------- /packages/list/src/prepend/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { curry2 } from '@typed/functions' 3 | import { length } from '../length' 4 | 5 | /** 6 | * Puts at value at the beginning of a `List`. 7 | * @name prepend(value: A, list: List): Array 8 | */ 9 | export const prepend = curry2(__prepend) 10 | 11 | function __prepend(value: A, list: List): Array { 12 | const itemCount = length(list) + 1 13 | const newList = Array(itemCount) 14 | 15 | newList[0] = value 16 | 17 | for (let i = 1; i < itemCount; ++i) newList[i] = list[i - 1] 18 | 19 | return newList 20 | } 21 | -------------------------------------------------------------------------------- /packages/list/src/prepend/prepend.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { prepend } from './' 4 | 5 | export const test: Test = describe(`prepend`, [ 6 | given(`a value and a list`, [ 7 | it(`returns a new list`, ({ equal }) => { 8 | equal([1, 2, 3], prepend(1, [2, 3])) 9 | }), 10 | ]), 11 | ]) 12 | -------------------------------------------------------------------------------- /packages/list/src/range/index.ts: -------------------------------------------------------------------------------- 1 | export * from './range' 2 | -------------------------------------------------------------------------------- /packages/list/src/range/range.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { range } from './range' 4 | 5 | export const test: Test = describe(`range`, [ 6 | given(`a number from and a number to`, [ 7 | it(`returns a list containing from and to values`, ({ equal }) => { 8 | equal([1, 2, 3], range(1, 4)) 9 | equal([0, 1, 2, 3, 4, 5], range(0)(6)) 10 | }), 11 | ]), 12 | ]) 13 | -------------------------------------------------------------------------------- /packages/list/src/range/range.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { curry2 } from '@typed/functions' 3 | 4 | /** 5 | * Creates a `List` that contains the numbers `from` to the 1 less than the 6 | * number `to`. 7 | * @name range(from: number, to: number): Array 8 | * @example 9 | * import { range, equals } from '167' 10 | * 11 | * const xs = range(1, 4) 12 | * const ys = [1, 2, 3] 13 | * 14 | * equals(xs, ys) // true 15 | */ 16 | export const range: Range = curry2(__range) 17 | 18 | export type Range = { 19 | (from: number, to: number): Array 20 | (from: number): (to: number) => List 21 | } 22 | 23 | function __range(from: number, to: number): Array { 24 | const length = to - from 25 | const list = Array(length) 26 | 27 | for (let i = 0; i < length; ++i) list[i] = i + from 28 | 29 | return list 30 | } 31 | -------------------------------------------------------------------------------- /packages/list/src/reduce/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './reduce' 3 | -------------------------------------------------------------------------------- /packages/list/src/reduce/reduce.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { ReduceArity3 } from './types' 3 | import { curry3 } from '@typed/functions' 4 | 5 | /** 6 | * Fold over the values in an array-like object. 7 | * @name reduce(f: (accumulator: B, value: A) => B, seed: B, list: List): B 8 | */ 9 | export const reduce: ReduceArity3 = curry3(function reduce( 10 | f: (acc: B, value: A, index: number) => B, 11 | seed: B, 12 | list: List 13 | ) { 14 | const length = list.length 15 | let acc: B = seed 16 | 17 | for (let i = 0; i < length; ++i) acc = f(acc, list[i], i) 18 | 19 | return acc 20 | }) 21 | -------------------------------------------------------------------------------- /packages/list/src/reduce/types.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | /** 4 | * @name Reducer 5 | * @type 6 | */ 7 | export type Reducer = (seed: B, value: A, index: number) => B 8 | 9 | export type ReduceArity3 = { 10 | (f: Reducer, seed: B, list: List): B 11 | (f: Reducer, seed: B): ReduceArity1 12 | (f: Reducer): ReduceArity2 13 | } 14 | 15 | export type ReduceArity2 = { 16 | (seed: B, list: List): B 17 | (seed: B): ReduceArity1 18 | } 19 | 20 | export type ReduceArity1 = { 21 | (list: List): B 22 | } 23 | -------------------------------------------------------------------------------- /packages/list/src/reduceRight/index.ts: -------------------------------------------------------------------------------- 1 | import { ReduceRightArity3, RightReducer } from './types' 2 | 3 | import { List } from '../types' 4 | import { curry3 } from '@typed/functions' 5 | 6 | /** 7 | * Fold over the values in a list from right-to-left. 8 | * @name reduceRight(f: (value: A, acc: B) => B, seed: B, list: List): B 9 | */ 10 | export const reduceRight: ReduceRightArity3 = curry3(function reduce( 11 | f: RightReducer, 12 | seed: B, 13 | list: List 14 | ): B { 15 | const length = list.length 16 | let acc: B = seed 17 | 18 | for (let i = length - 1; i >= 0; --i) acc = f(list[i], acc, i) 19 | 20 | return acc 21 | }) 22 | -------------------------------------------------------------------------------- /packages/list/src/reduceRight/types.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | /** 4 | * @name RightReducer 5 | * @type 6 | */ 7 | export type RightReducer = (value: A, accumulator: B, index: number) => B 8 | 9 | export type ReduceRightArity3 = { 10 | (f: RightReducer, seed: B, list: List): B 11 | (f: RightReducer, seed: B): ReduceRightArity1 12 | (f: RightReducer): ReduceRightArity2 13 | } 14 | 15 | export interface ReduceRightArity2 { 16 | (seed: B, list: List): B 17 | (seed: B): ReduceRightArity1 18 | } 19 | 20 | export interface ReduceRightArity1 { 21 | (list: List): B 22 | } 23 | -------------------------------------------------------------------------------- /packages/list/src/remove/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './remove' 3 | -------------------------------------------------------------------------------- /packages/list/src/remove/remove.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { remove } from './remove' 4 | 5 | export const test: Test = describe(`remove`, [ 6 | given(`int -> int -> List a`, [ 7 | it(`returns List a`, ({ equal }) => { 8 | equal(remove(2, 3, [1, 2, 3, 4, 5, 6, 7, 8]), [1, 2, 6, 7, 8]) 9 | equal(remove(2, 3, [1, 2, 3, 4, 5, 6, 7, 8]).length, 5) 10 | equal(remove(2, 100, [1, 2, 3, 4]), [1, 2]) 11 | equal(remove(2, 100, [1, 2, 3, 4]).length, 2) 12 | equal(remove(2, 1, [1, 2, 3]), [1, 2]) 13 | equal(remove(2, 1, [1, 2, 3]).length, 2) 14 | equal(remove(0, 1, [1, 2, 3, 4]), [2, 3, 4]) 15 | equal(remove(0, 1, [1, 2, 3, 4]).length, 3) 16 | // Fix issue #1 17 | equal(remove(3, 1, [1, 2, 3]), [1, 2, 3]) 18 | equal(remove(4, 1, [1, 2, 3]), [1, 2, 3]) 19 | }), 20 | ]), 21 | ]) 22 | -------------------------------------------------------------------------------- /packages/list/src/remove/remove.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { RemoveArity3 } from './types' 3 | import { arrayFrom } from '../arrayFrom' 4 | import { curry3 } from '@typed/functions' 5 | import { equals } from '@typed/logic' 6 | 7 | const isZero = equals(0) 8 | 9 | /** 10 | * Removes items from a `List`. 11 | * @name remove(index: number, amount: number, list: List): Array 12 | */ 13 | export const remove = curry3(function remove( 14 | index: number, 15 | amount: number, 16 | list: List 17 | ): Array { 18 | const length = list.length 19 | 20 | if (isZero(amount) || isZero(length) || index >= length) return arrayFrom(list) 21 | if (isZero(index) && amount >= length) return [] 22 | 23 | const newList = Array(length - Math.abs(index) - 1) 24 | 25 | for (let i = 0; i < index; ++i) newList[i] = list[i] 26 | 27 | for (let i = index + amount; i < length; ++i) newList[i - amount] = list[i] 28 | 29 | return newList 30 | }) as RemoveArity3 31 | -------------------------------------------------------------------------------- /packages/list/src/remove/types.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | export type RemoveArity3 = { 4 | (index: number, amount: number, list: List): Array 5 | (index: number, amount: number): RemoveArity1 6 | (index: number): RemoveArity2 7 | } 8 | 9 | export type RemoveArity2 = { 10 | (amount: number, list: List): Array 11 | (amount: number): RemoveArity1 12 | } 13 | 14 | export type RemoveArity1 = (list: List) => Array 15 | -------------------------------------------------------------------------------- /packages/list/src/reverse/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './reverse' 3 | -------------------------------------------------------------------------------- /packages/list/src/reverse/reverse.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { reverse } from './reverse' 4 | 5 | export const test: Test = describe(`reverse`, [ 6 | given(`an List a`, [ 7 | it(`returns a reversed array`, ({ equal }) => { 8 | const list = [1, 2, 3, 4, 5] 9 | const expected = [5, 4, 3, 2, 1] 10 | 11 | equal(expected, reverse(list)) 12 | }), 13 | ]), 14 | 15 | given(`a string`, [ 16 | it(`returns a reversed array`, ({ equal }) => { 17 | const str = 'hello' 18 | const expected = ['o', 'l', 'l', 'e', 'h'] 19 | 20 | equal(expected, reverse(str)) 21 | }), 22 | ]), 23 | ]) 24 | -------------------------------------------------------------------------------- /packages/list/src/reverse/reverse.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { Reverse } from './types' 3 | import { length } from '../length' 4 | 5 | /** 6 | * Reverses the order of values contained in a List. 7 | * @name reverse(list: List): Array 8 | */ 9 | export const reverse: Reverse = function reverse(list: List): Array { 10 | const itemCount = length(list) 11 | const reversedList = Array(itemCount) 12 | 13 | for (let index = 0; index < itemCount; ++index) reversedList[index] = list[itemCount - 1 - index] 14 | 15 | return reversedList 16 | } 17 | -------------------------------------------------------------------------------- /packages/list/src/reverse/types.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | export type Reverse = { 4 | (list: List): Array 5 | } 6 | -------------------------------------------------------------------------------- /packages/list/src/slice/index.ts: -------------------------------------------------------------------------------- 1 | import { Maybe, fromMaybe } from '@typed/maybe' 2 | 3 | import { List } from '../types' 4 | import { arrayFrom } from '../arrayFrom' 5 | import { curry3 } from '@typed/functions' 6 | 7 | /** 8 | * Slices a list between two indexes. 9 | * @name slice(start: number, end: Maybe, list: List): Array 10 | */ 11 | export const slice: Slice = curry3(__slice) 12 | 13 | export type Slice = { 14 | (startIndex: number, endIndex: Maybe, list: List): Array 15 | (startIndex: number, endIndex: Maybe): (list: List) => Array 16 | (startIndex: number): { 17 | (endIndex: Maybe, list: List): Array 18 | (endIndex: Maybe): (list: List) => Array 19 | } 20 | } 21 | 22 | function __slice(startIndex: number, endIndex: Maybe, list: List): Array { 23 | return arrayFrom(list).slice(startIndex, fromMaybe(void 0, endIndex)) 24 | } 25 | -------------------------------------------------------------------------------- /packages/list/src/sort/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { arrayFrom } from '../arrayFrom' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Sorts a `List`. 7 | * @name sort(comparator: Comparator, list: List): Array 8 | */ 9 | export const sort: Sort = curry2(__sort) 10 | 11 | export type Sort = { 12 | (comparator: (a: A, b: A) => number, list: List): Array 13 | (comparator: (a: A, b: A) => number): (list: List) => Array 14 | } 15 | 16 | function __sort(comparator: (a: A, b: A) => number, list: List): Array { 17 | return arrayFrom(list) 18 | .slice(0) 19 | .sort(comparator) 20 | } 21 | -------------------------------------------------------------------------------- /packages/list/src/splitAt/index.ts: -------------------------------------------------------------------------------- 1 | export * from './splitAt' 2 | -------------------------------------------------------------------------------- /packages/list/src/splitAt/splitAt.ts: -------------------------------------------------------------------------------- 1 | import { Index, List } from '../types' 2 | 3 | import { Maybe } from '@typed/maybe' 4 | import { curry2 } from '@typed/functions' 5 | import { slice } from '../slice' 6 | 7 | /** 8 | * Splits a `List` at a given index. 9 | * @name splitAt(index: Index, list: List): [List, List] 10 | */ 11 | export const splitAt: SplitAt = curry2(__splitAt) 12 | 13 | export type SplitAt = { 14 | (index: Index, list: List): [List, List] 15 | (index: Index): (list: List) => [List, List] 16 | } 17 | 18 | function __splitAt(index: Index, list: List): [List, List] { 19 | return [slice(0, Maybe.of(index), list), slice(index, Maybe.of(list.length), list)] 20 | } 21 | -------------------------------------------------------------------------------- /packages/list/src/splitEvery/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { Maybe } from '@typed/maybe' 3 | import { arrayFrom } from '../arrayFrom' 4 | import { curry2 } from '@typed/functions' 5 | import { slice } from '../slice' 6 | 7 | /** 8 | * Splits a list into a list of lists containing `n` number of values. 9 | * @name splitEvery(amount: number, list: List): Array> 10 | */ 11 | export const splitEvery: SplitEvery = curry2(function splitEvery( 12 | amount: number, 13 | list: List 14 | ): Array> { 15 | if (amount <= 0) return [arrayFrom(list)] 16 | 17 | const result: Array> = [] 18 | let i = 0 19 | 20 | while (i < list.length) result.push(slice(i, Maybe.of((i += amount)), list)) 21 | 22 | return result 23 | }) 24 | 25 | export type SplitEvery = { 26 | (amount: number, list: List): Array> 27 | (amount: number): (list: List) => Array> 28 | } 29 | -------------------------------------------------------------------------------- /packages/list/src/startsWith/index.ts: -------------------------------------------------------------------------------- 1 | export * from './startsWith' 2 | -------------------------------------------------------------------------------- /packages/list/src/startsWith/startsWith.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { startsWith } from './startsWith' 4 | 5 | export const test: Test = describe(`endsWith`, [ 6 | given(`an search string and a string starting in search`, [ 7 | it(`returns true`, ({ ok }) => { 8 | const search = 'he' 9 | const str = 'hello' 10 | 11 | ok(startsWith(search, str)) 12 | }), 13 | ]), 14 | 15 | given(`an search string and a string not starting in search`, [ 16 | it(`returns false`, ({ notOk }) => { 17 | const search = 'no' 18 | const str = 'hello' 19 | 20 | notOk(startsWith(search, str)) 21 | }), 22 | ]), 23 | ]) 24 | -------------------------------------------------------------------------------- /packages/list/src/startsWith/startsWith.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { curry2 } from '@typed/functions' 3 | import { length } from '../length' 4 | 5 | /** 6 | * Returns true if a List starts with a search value. 7 | * @name startsWith(search: List, list: List): boolean 8 | */ 9 | export const startsWith: StartsWith = curry2(__startsWith) 10 | 11 | export type StartsWith = { 12 | (search: List, list: List): boolean 13 | (search: List): (list: List) => boolean 14 | } 15 | 16 | function __startsWith(search: List, list: List): boolean { 17 | const searchCount = length(search) 18 | 19 | for (let i = 0; i < searchCount; ++i) if (search[i] !== list[i]) return false 20 | 21 | return true 22 | } 23 | -------------------------------------------------------------------------------- /packages/list/src/take/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { Maybe } from '@typed/maybe' 3 | import { curry2 } from '@typed/functions' 4 | import { slice } from '../slice' 5 | 6 | /** 7 | * Takes the first `n` items of a `List`. 8 | * @name take(n: number, list: List): Array 9 | */ 10 | export const take: Take = curry2((n: number, list: List) => 11 | slice(0, Maybe.of(n < 0 ? Infinity : n), list) 12 | ) 13 | 14 | export type Take = { 15 | (n: number, list: List): Array 16 | (n: number): (list: List) => Array 17 | } 18 | -------------------------------------------------------------------------------- /packages/list/src/takeLast/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { curry2 } from '@typed/functions' 3 | import { drop } from '../drop' 4 | 5 | /** 6 | * Takes the last `n` values from a `List`. 7 | * @name takeLast(n: number, list: List): Array 8 | */ 9 | export const takeLast: TakeLast = curry2((n: number, list: List) => 10 | drop(n >= 0 ? list.length - n : 0, list) 11 | ) 12 | 13 | export type TakeLast = { 14 | (n: number, list: List): Array 15 | (n: number): (list: List) => Array 16 | } 17 | -------------------------------------------------------------------------------- /packages/list/src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An immutable List type. 3 | * @name List 4 | * @type 5 | */ 6 | export interface List extends ArrayLike {} 7 | 8 | /** 9 | * A type-alias for numbers being used as indexes for a List. 10 | * @name Index 11 | * @type 12 | */ 13 | export type Index = number 14 | -------------------------------------------------------------------------------- /packages/list/src/uniq/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { arrayFrom } from '../arrayFrom' 3 | 4 | /** 5 | * Returns a `List` of unique values. 6 | * @name uniq(list: List): Array 7 | */ 8 | export function uniq(list: List): Array { 9 | return arrayFrom(new Set(arrayFrom(list))) 10 | } 11 | -------------------------------------------------------------------------------- /packages/list/src/update/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './update' 3 | -------------------------------------------------------------------------------- /packages/list/src/update/types.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | export type UpdateArity3 = { 4 | (index: number, value: A, list: List): Array 5 | 6 | (index: number): UpdateArity2 7 | (index: number, value: A): UpdateArity1 8 | } 9 | 10 | export type UpdateArity2 = { 11 | (value: A, list: List): Array 12 | (value: A): UpdateArity1 13 | } 14 | 15 | export type UpdateArity1 = { 16 | (list: List): Array 17 | } 18 | -------------------------------------------------------------------------------- /packages/list/src/update/update.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { update } from './update' 4 | 5 | export const test: Test = describe(`update`, [ 6 | given(`int -> a -> [a]`, [ 7 | it(`return a`, ({ equal }) => { 8 | const list = [1, 2, 3, 4, 5, 6, 7, 8] 9 | 10 | equal(update(-1, 1, list), list) 11 | equal(update(20, 1, list), list) 12 | equal(update(8, 9, list), list) 13 | equal(update(0, 10, list), [10, 2, 3, 4, 5, 6, 7, 8]) 14 | equal(update(0, 1, []), []) 15 | }), 16 | ]), 17 | ]) 18 | -------------------------------------------------------------------------------- /packages/list/src/update/update.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { UpdateArity3 } from './types' 3 | import { arrayFrom } from '../arrayFrom' 4 | import { curry3 } from '@typed/functions' 5 | 6 | /** 7 | * Updates the value of an array 8 | * @name update(index: number, value: A, list: Li) 9 | */ 10 | export const update: UpdateArity3 = curry3(function update( 11 | index: number, 12 | value: A, 13 | list: List 14 | ): Array { 15 | const length = list.length 16 | const newList = arrayFrom(list) 17 | 18 | if (length === 0 || index < 0 || index >= length) return newList 19 | 20 | newList[index] = value 21 | 22 | return newList 23 | }) 24 | -------------------------------------------------------------------------------- /packages/list/src/without/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | import { arrayFrom } from '../arrayFrom' 3 | import { curry2 } from '@typed/functions' 4 | import { filter } from '../filter' 5 | 6 | /** 7 | * Returns a list without the specified values. 8 | * @name without(values: List, list: List): Array 9 | */ 10 | export const without: Without = curry2(__without) 11 | 12 | export type Without = { 13 | (values: List, list: List): Array 14 | (values: List): (list: List) => Array 15 | } 16 | 17 | function __without(values: List, list: List): Array { 18 | const set = new Set(arrayFrom(values)) 19 | 20 | return filter(x => !set.has(x), list) 21 | } 22 | -------------------------------------------------------------------------------- /packages/logic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/logic", 3 | "version": "3.0.0", 4 | "description": "Collection of functions to perform logic", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only logic", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:browser": "yarn test:unit -- --browser", 14 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 15 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 16 | }, 17 | "dependencies": { 18 | "@typed/either": "4.0.0", 19 | "@typed/functions": "3.0.0", 20 | "@typed/maybe": "7.0.0" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/TylorS/typed.git" 25 | }, 26 | "keywords": [ 27 | "typed", 28 | "logic", 29 | "typescript" 30 | ], 31 | "author": "Tylor Steinberger ", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/TylorS/typed/issues" 35 | }, 36 | "homepage": "https://github.com/TylorS/typed#readme" 37 | } 38 | -------------------------------------------------------------------------------- /packages/logic/src/all/index.ts: -------------------------------------------------------------------------------- 1 | import { List, Predicate } from '../types' 2 | 3 | import { curry2 } from '@typed/functions' 4 | import { not } from '../not' 5 | 6 | /** 7 | * Returns `true` if predicate function returns `true` for all values in a `List`. 8 | * @name all(predicate: Predicate, list: List): boolean 9 | */ 10 | export const all = curry2(__all) 11 | 12 | function __all(predicate: Predicate, list: List): boolean { 13 | for (let i = 0; i < list.length; ++i) if (not(predicate(list[i]))) return false 14 | 15 | return true 16 | } 17 | -------------------------------------------------------------------------------- /packages/logic/src/allPass/index.ts: -------------------------------------------------------------------------------- 1 | import { List, Predicate } from '../types' 2 | 3 | import { curry2 } from '@typed/functions' 4 | import { not } from '../not' 5 | 6 | /** 7 | * Returns true if all predicates return true. 8 | * @name allPass(predicates: List>, value: A): boolean 9 | */ 10 | export const allPass: AllPass = curry2(__allPass) 11 | 12 | export type AllPass = { 13 | (predicates: List>, value: A): boolean 14 | (predicates: List>): Predicate 15 | } 16 | 17 | function __allPass(predicates: List>, value: A): boolean { 18 | const predicateCount = predicates.length 19 | 20 | for (let i = 0; i < predicateCount; ++i) if (not(predicates[i](value))) return false 21 | 22 | return true 23 | } 24 | -------------------------------------------------------------------------------- /packages/logic/src/and/index.ts: -------------------------------------------------------------------------------- 1 | import { Predicate } from '../types' 2 | import { curry3 } from '@typed/functions' 3 | 4 | /** 5 | * Applies `&&` between two predicate functions. 6 | * @name and(predicate1: Predicate, predicate2: Predicate, value: A): boolean 7 | */ 8 | export const and: And = curry3(__and) 9 | 10 | export type And = { 11 | (predicate1: Predicate, predicate2: Predicate, value: A): boolean 12 | (predicate1: Predicate, predicate2: Predicate): Predicate 13 | (predicate1: Predicate): { 14 | (predicate2: Predicate, value: A): boolean 15 | (predicate2: Predicate): Predicate 16 | } 17 | } 18 | 19 | function __and(predicate1: Predicate, predicate2: Predicate, value: A): boolean { 20 | return predicate1(value) && predicate2(value) 21 | } 22 | -------------------------------------------------------------------------------- /packages/logic/src/any/any.ts: -------------------------------------------------------------------------------- 1 | import { List, Predicate } from '../types' 2 | 3 | import { Any } from './types' 4 | import { curry2 } from '@typed/functions' 5 | 6 | /** 7 | * Returns `true` if predicate function returns `true` for any value contained 8 | * in a `List`. 9 | * @name any(predicate: Predicate, list: List): boolean 10 | */ 11 | export const any: Any = curry2(__any) 12 | 13 | function __any(predicate: Predicate, list: List): boolean { 14 | for (let i = 0; i < list.length; ++i) if (predicate(list[i])) return true 15 | 16 | return false 17 | } 18 | -------------------------------------------------------------------------------- /packages/logic/src/any/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './any' 3 | -------------------------------------------------------------------------------- /packages/logic/src/any/types.ts: -------------------------------------------------------------------------------- 1 | import { List, Predicate } from '../types' 2 | 3 | export type Any = { 4 | (predicate: Predicate, list: List): boolean 5 | (predicate: Predicate): (list: List) => boolean 6 | } 7 | -------------------------------------------------------------------------------- /packages/logic/src/anyPass/index.ts: -------------------------------------------------------------------------------- 1 | import { List, Predicate } from '../types' 2 | 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Returns true if any predicates returns true, false otherwise. 7 | * @name anyPass(predicates: List>, value: A): boolean 8 | */ 9 | export const anyPass: AnyPass = curry2(__anyPass) 10 | 11 | export type AnyPass = { 12 | (predicates: List>, value: A): boolean 13 | (predicates: List>): (value: A) => boolean 14 | } 15 | 16 | function __anyPass(predicates: List>, value: A): boolean { 17 | for (const predicate of Array.from(predicates)) if (predicate(value)) return true 18 | 19 | return false 20 | } 21 | -------------------------------------------------------------------------------- /packages/logic/src/cond/cond.ts: -------------------------------------------------------------------------------- 1 | import { Cond, Conditional } from './types' 2 | import { Maybe, Nothing } from '@typed/maybe' 3 | 4 | import { List } from '../types' 5 | import { curry2 } from '@typed/functions' 6 | 7 | /** 8 | * Returns the value of an applied `Conditional`. If no `Conditional` is matched 9 | * then `Nothing` is returned. 10 | * @name cond(conditionals: List>, value: A): Maybe 11 | */ 12 | export const cond: Cond = curry2(__cond) 13 | 14 | function __cond(conditionals: List>, value: A): Maybe { 15 | const itemCount = conditionals.length 16 | 17 | for (let i = 0; i < itemCount; ++i) { 18 | const [predicate, f] = conditionals[i] 19 | 20 | if (predicate(value)) return Maybe.of(f(value)) 21 | } 22 | 23 | return Nothing 24 | } 25 | -------------------------------------------------------------------------------- /packages/logic/src/cond/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './cond' 3 | -------------------------------------------------------------------------------- /packages/logic/src/cond/types.ts: -------------------------------------------------------------------------------- 1 | import { List, Predicate } from '../types' 2 | 3 | import { Maybe } from '@typed/maybe' 4 | 5 | export type Cond = { 6 | (conditions: List>, value: A): Maybe 7 | (conditions: List>): (value: A) => Maybe 8 | } 9 | 10 | /** 11 | * @name Conditional 12 | * @type 13 | */ 14 | export type Conditional = [Predicate, (value: A) => B] 15 | -------------------------------------------------------------------------------- /packages/logic/src/equals/equals.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { equals } from './equals' 4 | 5 | export const test: Test = describe(`equal`, [ 6 | given(`given two arrays with equal values`, [ 7 | it(`returns true`, ({ ok }) => { 8 | const a = [0, 1, 2, 3] 9 | const b = [0, 1, 2, 3] 10 | 11 | ok(equals(a, b)) 12 | }), 13 | ]), 14 | ]) 15 | -------------------------------------------------------------------------------- /packages/logic/src/equals/functionName/functionName.ts: -------------------------------------------------------------------------------- 1 | const FUNCTION_NAME_REGEX = /^function\s*([\w$]+)/ 2 | 3 | const DEFAULT_MATCH = ['', ''] 4 | 5 | /** 6 | * Returns the name of a function. 7 | * @name functionName(fn: Function): string 8 | */ 9 | export function functionName(fn: Function): string { 10 | if (fn.name) return fn.name 11 | 12 | const [, name] = String(fn).match(FUNCTION_NAME_REGEX) || DEFAULT_MATCH 13 | 14 | return name 15 | } 16 | -------------------------------------------------------------------------------- /packages/logic/src/equals/functionName/index.ts: -------------------------------------------------------------------------------- 1 | export * from './functionName' 2 | -------------------------------------------------------------------------------- /packages/logic/src/equals/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './equals' 3 | -------------------------------------------------------------------------------- /packages/logic/src/equals/typeOf/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typeOf' 2 | -------------------------------------------------------------------------------- /packages/logic/src/equals/typeOf/typeOf.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TylorS/typed-unmaintained/ce54f35416a8057d345f4f5ca6caf4c761643176/packages/logic/src/equals/typeOf/typeOf.test.ts -------------------------------------------------------------------------------- /packages/logic/src/equals/typeOf/typeOf.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the type of a value. 3 | * @name typeOf(value: any): string 4 | */ 5 | export function typeOf(value: string): 'String' 6 | export function typeOf(value: number): 'Number' 7 | export function typeOf(value: null): 'Null' 8 | export function typeOf(value: undefined): 'Undefined' 9 | export function typeOf(value: undefined): 'Undefined' 10 | export function typeOf(value: any): string 11 | export function typeOf(value: any): string { 12 | if (value === null) return 'Null' 13 | 14 | if (value === void 0) return `Undefined` 15 | 16 | return Object.prototype.toString.call(value).slice(8, -1) 17 | } 18 | -------------------------------------------------------------------------------- /packages/logic/src/equals/types.ts: -------------------------------------------------------------------------------- 1 | export type Equals = { 2 | (a: A, b: A): boolean 3 | (a: A): (b: A) => boolean 4 | } 5 | -------------------------------------------------------------------------------- /packages/logic/src/greaterThan/index.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Applies `>` to 2 values. 5 | * @name greaterThan(right: A, left: A): boolean 6 | */ 7 | export const greaterThan: GreaterThan = curry2((right: A, left: A) => left > right) 8 | 9 | export type GreaterThan = { 10 | (right: A, left: A): boolean 11 | (right: A): (left: A) => boolean 12 | } 13 | -------------------------------------------------------------------------------- /packages/logic/src/greaterThanOrEqual/index.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Applies `>=` to 2 values. 5 | * @name greaterThanOrEqual(right: A, left: A): boolean 6 | */ 7 | export const greaterThanOrEqual: GreaterThanOrEqual = curry2( 8 | (right: A, left: A) => left >= right 9 | ) 10 | 11 | export type GreaterThanOrEqual = { 12 | (right: A, left: A): boolean 13 | (right: A): (left: A) => boolean 14 | } 15 | -------------------------------------------------------------------------------- /packages/logic/src/ifElse/ifElse.ts: -------------------------------------------------------------------------------- 1 | import { IfElseArity4 } from './types' 2 | import { curry4 } from '@typed/functions' 3 | 4 | /** 5 | * Function for handling if/Else statements. 6 | * @name ifElse(predicate: Predicate, thenF: Arity1, elseF: Arity1, value: A): B 7 | */ 8 | export const ifElse: IfElseArity4 = curry4(__ifElse) 9 | 10 | function __ifElse( 11 | ifF: (value: A) => boolean, 12 | thenF: (value: A) => B, 13 | elseF: (value: A) => B, 14 | value: A 15 | ): B { 16 | return ifF(value) ? thenF(value) : elseF(value) 17 | } 18 | -------------------------------------------------------------------------------- /packages/logic/src/ifElse/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './ifElse' 3 | -------------------------------------------------------------------------------- /packages/logic/src/ifElse/types.ts: -------------------------------------------------------------------------------- 1 | import { Arity1 } from '@typed/functions' 2 | import { Predicate } from '../types' 3 | 4 | export type IfElseArity4 = { 5 | (predicate: Predicate, thenF: Arity1, elseF: Arity1, value: A): B 6 | (predicate: Predicate, thenF: Arity1, elseF: Arity1): IfElseArity1 7 | (predicate: Predicate, thenF: Arity1): IfElseArity2 8 | (predicate: Predicate): IfElseArity3 9 | } 10 | 11 | export type IfElseArity3 = { 12 | (thenF: Arity1, elseF: Arity1, value: A): B 13 | (thenF: Arity1, elseF: Arity1): IfElseArity1 14 | (thenF: Arity1): IfElseArity2 15 | } 16 | 17 | export type IfElseArity2 = { 18 | (elseF: Arity1, value: A): B 19 | (elseF: Arity1): IfElseArity1 20 | } 21 | 22 | export type IfElseArity1 = { 23 | (value: A): B 24 | } 25 | -------------------------------------------------------------------------------- /packages/logic/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | 3 | export * from './all' 4 | export * from './allPass' 5 | export * from './and' 6 | export * from './any' 7 | export * from './anyPass' 8 | export * from './cond' 9 | export * from './equals' 10 | export * from './greaterThan' 11 | export * from './greaterThanOrEqual' 12 | export * from './ifElse' 13 | export * from './is' 14 | export * from './lessThan' 15 | export * from './lessThanOrEqual' 16 | export * from './not' 17 | export * from './or' 18 | export * from './propEq' 19 | export * from './propOr' 20 | export * from './tryCatch' 21 | -------------------------------------------------------------------------------- /packages/logic/src/is/index.ts: -------------------------------------------------------------------------------- 1 | export * from './isArray' 2 | export * from './isIterable' 3 | export * from './isIterator' 4 | export * from './isList' 5 | export * from './isMap' 6 | export * from './isNull' 7 | export * from './isNumber' 8 | export * from './isObject' 9 | export * from './isPromiseLike' 10 | export * from './isSet' 11 | export * from './isUndefined' 12 | -------------------------------------------------------------------------------- /packages/logic/src/is/isArray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if given value is an Array. 3 | * @name isArray(x: any): x is Array 4 | */ 5 | export function isArray(x: any): x is Array { 6 | return Array.isArray(x) 7 | } 8 | -------------------------------------------------------------------------------- /packages/logic/src/is/isIterable.ts: -------------------------------------------------------------------------------- 1 | import { isIterator } from './isIterator' 2 | 3 | /** 4 | * Returns true if a value is an Iterable. 5 | * @name isIterable(x: any): x is Iterable 6 | */ 7 | export function isIterable(x: any): x is Iterable { 8 | return x && typeof x[Symbol.iterator] === 'function' && isIterator(x[Symbol.iterator]()) 9 | } 10 | -------------------------------------------------------------------------------- /packages/logic/src/is/isIterator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if value is an iterator. 3 | * @name isIterator(x: any): x is Iterator 4 | */ 5 | export function isIterator(x: any): x is Iterator { 6 | return x && typeof (x as Iterator).next === 'function' 7 | } 8 | -------------------------------------------------------------------------------- /packages/logic/src/is/isList.ts: -------------------------------------------------------------------------------- 1 | import { List } from '../types' 2 | 3 | /** 4 | * Returns true if a value is a `List`. 5 | * @name isList(x: any): x is List 6 | */ 7 | export function isList(x: any): x is List { 8 | if (Array.isArray(x)) return true 9 | if (!x || typeof x !== 'object' || typeof x === 'string') return false 10 | if (x.nodeType === 1) return !!x.length 11 | if (x.length === 0) return true 12 | if (x.length > 0) return x.hasOwnProperty(0) && x.hasOwnProperty(x.length - 1) 13 | return false 14 | } 15 | -------------------------------------------------------------------------------- /packages/logic/src/is/isMap.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if a value is a `Map`. 3 | * @name isMap(x: any): x is Map 4 | */ 5 | export function isMap(x: any): x is Map { 6 | return ( 7 | x && typeof (x as Map).delete === 'function' && typeof (x as Map).set === 'function' 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /packages/logic/src/is/isNull.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if the value is null 3 | * @name isNull(x: any): x is null 4 | */ 5 | export function isNull(x: any): x is null { 6 | return x === null 7 | } 8 | -------------------------------------------------------------------------------- /packages/logic/src/is/isNumber.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if value is a number. 3 | * @name isNumber(x: any): x is number 4 | */ 5 | export function isNumber(x: any): x is number { 6 | return typeof x === 'number' 7 | } 8 | -------------------------------------------------------------------------------- /packages/logic/src/is/isObject.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if a value is an object. 3 | * @name isObject(x: any): x is A 4 | */ 5 | export function isObject(x: any): x is A { 6 | return x && typeof x === 'object' 7 | } 8 | -------------------------------------------------------------------------------- /packages/logic/src/is/isPromiseLike.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if a value is PromiseLike 3 | * @name isPromiseLike(x: any): x is PromiseLike 4 | */ 5 | export function isPromiseLike(x: any): x is PromiseLike { 6 | return x && typeof x.then === 'function' 7 | } 8 | -------------------------------------------------------------------------------- /packages/logic/src/is/isSet.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if a value is a Set. 3 | * @name isSet(x: any): x is Set 4 | */ 5 | export function isSet(x: any): x is Set { 6 | return x && typeof (x as Set).delete === 'function' && typeof (x as Set).add === 'function' 7 | } 8 | -------------------------------------------------------------------------------- /packages/logic/src/is/isUndefined.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if the value is undefined. 3 | * @name isUndefined(x: any): x is undefined 4 | */ 5 | export function isUndefined(x: any): x is undefined { 6 | return x === void 0 7 | } 8 | -------------------------------------------------------------------------------- /packages/logic/src/lessThan/index.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Compares two values using `<` 5 | * @name lessThan(right: A, left: A): boolean 6 | */ 7 | export const lessThan: LessThan = curry2((right: A, left: A) => left < right) 8 | 9 | export type LessThan = { 10 | (right: A, left: A): boolean 11 | (right: A): (left: A) => boolean 12 | } 13 | -------------------------------------------------------------------------------- /packages/logic/src/lessThanOrEqual/index.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Compares 2 values using `<=` 5 | * @name lessThanOrEqual(right: A, left: A): boolean 6 | */ 7 | export const lessThanOrEqual: LessThanOrEqual = curry2((right: A, left: A) => left <= right) 8 | 9 | export type LessThanOrEqual = { 10 | (right: A, left: A): boolean 11 | (right: A): (left: A) => boolean 12 | } 13 | -------------------------------------------------------------------------------- /packages/logic/src/not/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Applies `!` to a value. 3 | * @name not(a: A): boolean 4 | */ 5 | export function not(x: A): boolean { 6 | return !x 7 | } 8 | -------------------------------------------------------------------------------- /packages/logic/src/or/index.ts: -------------------------------------------------------------------------------- 1 | import { Predicate } from '../types' 2 | import { curry3 } from '@typed/functions' 3 | 4 | /** 5 | * Applies `||` between two predicate functions. 6 | * @name or(predicate1: Predicate, predicate2: Predicate, value: A): boolean 7 | */ 8 | export const or: Or = curry3(__or) 9 | 10 | export type Or = { 11 | (predicate1: Predicate, predicate2: Predicate, value: A): boolean 12 | (predicate1: Predicate, predicate2: Predicate): Predicate 13 | (predicate1: Predicate): { 14 | (predicate2: Predicate, value: A): boolean 15 | (predicate2: Predicate): Predicate 16 | } 17 | } 18 | 19 | function __or(predicate1: Predicate, predicate2: Predicate, value: A): boolean { 20 | return predicate1(value) || predicate2(value) 21 | } 22 | -------------------------------------------------------------------------------- /packages/logic/src/propEq/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './propEq' 3 | -------------------------------------------------------------------------------- /packages/logic/src/propEq/propEq.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { propEq } from './propEq' 4 | 5 | export const test: Test = describe(`propEq`, [ 6 | given(`a key`, [ 7 | it(`returns a function`, ({ equal }) => { 8 | const aProp = propEq('a') 9 | 10 | equal('function', typeof aProp) 11 | }), 12 | ]), 13 | 14 | given(`a key and a value`, [ 15 | it(`returns a function`, ({ equal }) => { 16 | const aPropIsOne = propEq('a', 1) 17 | 18 | equal('function', typeof aPropIsOne) 19 | }), 20 | ]), 21 | 22 | given(`a key, value and object`, [ 23 | it(`returns true if object has same value at specified key`, ({ ok }) => { 24 | interface Foo { 25 | readonly a: number 26 | readonly b: number 27 | } 28 | 29 | const testFoo: Foo = { a: 1, b: 2 } 30 | 31 | const aPropIsOne = propEq('a', 1) 32 | 33 | ok(aPropIsOne(testFoo)) 34 | }), 35 | 36 | it(`returns false if object does not have same value at specified key`, ({ notOk }) => { 37 | interface Foo { 38 | readonly a: number 39 | readonly b: number 40 | } 41 | 42 | const aPropIsOne = propEq('a', 1) 43 | 44 | const testFoo: Foo = { a: 2, b: 1 } 45 | 46 | notOk(aPropIsOne(testFoo)) 47 | }), 48 | ]), 49 | ]) 50 | -------------------------------------------------------------------------------- /packages/logic/src/propEq/propEq.ts: -------------------------------------------------------------------------------- 1 | import { PropEq } from './types' 2 | import { curry3 } from '@typed/functions' 3 | import { equals } from '../equals' 4 | 5 | /** 6 | * Returns `true` if a given object's key value is equal to the given `value`. 7 | * @name propEq(key: K, value: O[K], obj: O): boolean 8 | */ 9 | export const propEq: PropEq = curry3(function( 10 | key: K, 11 | value: O[K], 12 | obj: O 13 | ): boolean { 14 | return equals(obj[key], value) 15 | }) 16 | -------------------------------------------------------------------------------- /packages/logic/src/propEq/types.ts: -------------------------------------------------------------------------------- 1 | export type PropEq = { 2 | >>(key: K, value: A, object: O): boolean 3 | (key: K, value: A): >>(object: O) => boolean 4 | (key: K): { 5 | >>(value: A, object: O): boolean 6 | (value: A): >>(object: O) => boolean 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/logic/src/propOr/index.ts: -------------------------------------------------------------------------------- 1 | export * from './propOr' 2 | -------------------------------------------------------------------------------- /packages/logic/src/propOr/propOr.ts: -------------------------------------------------------------------------------- 1 | import { curry3 } from '@typed/functions' 2 | 3 | /** 4 | * Returns the value at a given key if an object has that property, otherwise 5 | * returns the defaultValue. 6 | * @name propOr(defaultValue: A, key: K, obj: { [Key in K]: A }): A 7 | */ 8 | export const propOr: PropOr = curry3(__propOr) 9 | 10 | function __propOr(defaultValue: A, key: K, obj: { [Key in K]: A }): A { 11 | return obj.hasOwnProperty(key) ? obj[key] : defaultValue 12 | } 13 | 14 | export type PropOr = { 15 | (defaultValue: A, key: K, obj: { [Key in K]: A }): A 16 | (defaultValue: A, key: K): (obj: { [Key in K]: A }) => A 17 | (defaultValue: A): { 18 | (key: K, obj: { [Key in K]: A }): A 19 | (key: K): (obj: { [Key in K]: A }) => A 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/logic/src/tryCatch/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './tryCatch' 3 | -------------------------------------------------------------------------------- /packages/logic/src/tryCatch/tryCatch.ts: -------------------------------------------------------------------------------- 1 | import { Either } from '@typed/either' 2 | import { List } from '../types' 3 | import { TryCatch } from './types' 4 | import { curry2 } from '@typed/functions' 5 | 6 | /** 7 | * Given a list of arguments and a function, applies the function with 8 | * the given arguments. 9 | * @name tryCatch(list: List, fn: (...args: Array) => A): Either 10 | */ 11 | export const tryCatch = curry2(function apply( 12 | list: List, 13 | f: (...args: Array) => A 14 | ): Either { 15 | switch (list.length) { 16 | case 0: 17 | return __catch(f) 18 | case 1: 19 | return __catch(() => f(list[0])) 20 | case 2: 21 | return __catch(() => f(list[0], list[1])) 22 | case 3: 23 | return __catch(() => f(list[0], list[1], list[2])) 24 | case 4: 25 | return __catch(() => f(list[0], list[1], list[2], list[3])) 26 | case 5: 27 | return __catch(() => f(list[0], list[1], list[2], list[3], list[4])) 28 | default: 29 | return __catch(() => f.apply(null, list)) 30 | } 31 | }) as TryCatch 32 | 33 | function __catch(f: () => A): Either { 34 | try { 35 | return Either.of(f()) 36 | } catch (error) { 37 | return Either.left(error) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/logic/src/types.ts: -------------------------------------------------------------------------------- 1 | export type List = { 2 | readonly [key: number]: A 3 | readonly length: number 4 | } 5 | 6 | export type Predicate = (value: A) => boolean 7 | -------------------------------------------------------------------------------- /packages/math/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/math", 3 | "version": "3.0.0", 4 | "description": "Math related functions", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only math", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:browser": "yarn test:unit -- --browser", 14 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 15 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 16 | }, 17 | "dependencies": { 18 | "@typed/functions": "3.0.0", 19 | "@typed/list": "3.0.0", 20 | "@typed/maybe": "7.0.0" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/TylorS/typed.git" 25 | }, 26 | "keywords": [ 27 | "typed", 28 | "math", 29 | "function", 30 | "fp" 31 | ], 32 | "author": "Tylor Steinberger ", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/TylorS/typed/issues" 36 | }, 37 | "homepage": "https://github.com/TylorS/typed#readme" 38 | } 39 | -------------------------------------------------------------------------------- /packages/math/src/add/add.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Add 2 numbers together 5 | * @name add(x: number, y: number): number 6 | */ 7 | export const add = curry2(__add) 8 | 9 | function __add(x: number, y: number): number { 10 | return x + y 11 | } 12 | -------------------------------------------------------------------------------- /packages/math/src/add/index.ts: -------------------------------------------------------------------------------- 1 | export * from './add' 2 | -------------------------------------------------------------------------------- /packages/math/src/decrement/index.ts: -------------------------------------------------------------------------------- 1 | import { add } from '../add' 2 | 3 | /** 4 | * Subtracts `1` from a number. 5 | * @name decrement(num: number): number 6 | */ 7 | export const decrement = add(-1) 8 | -------------------------------------------------------------------------------- /packages/math/src/divide/index.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Divides 2 numbers 5 | * @name divide(x: number, y: number): number 6 | */ 7 | export const divide = curry2(__divide) 8 | 9 | function __divide(right: number, left: number): number { 10 | return left / right 11 | } 12 | -------------------------------------------------------------------------------- /packages/math/src/increment/index.ts: -------------------------------------------------------------------------------- 1 | import { add } from '../add' 2 | 3 | /** 4 | * Adds 1 to a number. 5 | * @name increment(num: number): number 6 | */ 7 | export const increment = add(1) 8 | -------------------------------------------------------------------------------- /packages/math/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './add' 2 | export * from './decrement' 3 | export * from './divide' 4 | export * from './increment' 5 | export * from './mean' 6 | export * from './median' 7 | export * from './modulo' 8 | export * from './multiply' 9 | export * from './negate' 10 | export * from './pow' 11 | export * from './product' 12 | export * from './subtract' 13 | export * from './sum' 14 | -------------------------------------------------------------------------------- /packages/math/src/mean/index.ts: -------------------------------------------------------------------------------- 1 | import { List } from '@typed/list' 2 | import { divide } from '../divide' 3 | import { sum } from '../sum' 4 | 5 | /** 6 | * Calculates the average of a list of numbers. 7 | * @name mean(numbers: List): number 8 | */ 9 | export const mean = (numbers: List) => divide(numbers.length, sum(numbers)) 10 | -------------------------------------------------------------------------------- /packages/math/src/mean/mean.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { mean } from './' 4 | 5 | export const test: Test = describe(`mean`, [ 6 | given(`a list of numbers`, [ 7 | it(`returns the mean`, ({ equal }) => { 8 | const list = [1, 2, 3, 4, 5] 9 | equal(3, mean(list)) 10 | }), 11 | ]), 12 | ]) 13 | -------------------------------------------------------------------------------- /packages/math/src/median/index.ts: -------------------------------------------------------------------------------- 1 | export * from './median' 2 | -------------------------------------------------------------------------------- /packages/math/src/median/median.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | import { fromJust, isJust, isNothing } from '@typed/maybe' 3 | 4 | import { median } from './median' 5 | 6 | export const test: Test = describe(`median`, [ 7 | given(`an empty list`, [ 8 | it(`returns Nothing`, ({ ok }) => { 9 | ok(isNothing(median([]))) 10 | }), 11 | ]), 12 | 13 | given(`a non-empty list`, [ 14 | it(`returns a Just containing the calculated median`, ({ equal }) => { 15 | const list = [1, 2, 2, 3, 3, 3, 3, 10] 16 | 17 | const actual = median(list) 18 | 19 | if (isJust(actual)) equal(3, fromJust(actual)) 20 | }), 21 | ]), 22 | ]) 23 | -------------------------------------------------------------------------------- /packages/math/src/median/median.ts: -------------------------------------------------------------------------------- 1 | import { List, ascend, slice, sort } from '@typed/list' 2 | import { Maybe, Nothing } from '@typed/maybe' 3 | 4 | import { mean } from '../mean' 5 | 6 | /** 7 | * Calculates the median of a `List`. If the calculated median is `NaN` 8 | * a `Nothing` is returned otherwise a `Just` containing the median will be returned. 9 | * @name median(numbers: List): Maybe 10 | */ 11 | export function median(numbers: List): Maybe { 12 | const length = numbers.length 13 | 14 | if (length === 0) return Nothing 15 | 16 | const width = 2 - (length % 2) 17 | const index = (length - width) / 2 18 | 19 | const medianNumbers = slice(index, Maybe.of(index + width), sort(ascend(x => x), numbers)) 20 | 21 | return numberToMaybe(mean(medianNumbers)) 22 | } 23 | 24 | function numberToMaybe(num: number): Maybe { 25 | return Number.isNaN(num) ? Nothing : Maybe.of(num) 26 | } 27 | -------------------------------------------------------------------------------- /packages/math/src/modulo/index.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Applies `%` to 2 numbers. 5 | * @name modulo(right: number, left: number): number 6 | */ 7 | export const modulo = curry2(__modulo) 8 | 9 | function __modulo(right: number, left: number): number { 10 | return left % right 11 | } 12 | -------------------------------------------------------------------------------- /packages/math/src/multiply/index.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Multiplies 2 numbers. 5 | * @name multiply(x: number, y: number): number 6 | */ 7 | export const multiply = curry2(__multiply) 8 | 9 | function __multiply(x: number, y: number): number { 10 | return x * y 11 | } 12 | -------------------------------------------------------------------------------- /packages/math/src/negate/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Negates a number. 3 | * @name negate(n: number): number 4 | */ 5 | export const negate = (n: number): number => -n 6 | -------------------------------------------------------------------------------- /packages/math/src/pow/index.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Applies a base to the exponent power. 5 | * @name pow(exponent: number, base: number): number 6 | */ 7 | export const pow: Pow = curry2(__pow) 8 | 9 | export type Pow = { 10 | (exponent: number, base: number): number 11 | (exponent: number): (base: number) => number 12 | } 13 | 14 | function __pow(exponent: number, base: number): number { 15 | return Math.pow(base, exponent) 16 | } 17 | -------------------------------------------------------------------------------- /packages/math/src/product/index.ts: -------------------------------------------------------------------------------- 1 | import { List, reduce } from '@typed/list' 2 | 3 | import { multiply } from '../multiply' 4 | 5 | /** 6 | * Calculates the produce of a list of numbers. 7 | * @name produce(numbers: List): number 8 | */ 9 | export const product: (numbers: List) => number = reduce(multiply, 1) 10 | -------------------------------------------------------------------------------- /packages/math/src/subtract/index.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Subtracts one number from another. 5 | * @name subtract(right: number, left: number): number 6 | */ 7 | export const subtract = curry2(__subtract) 8 | 9 | function __subtract(right: number, left: number): number { 10 | return left - right 11 | } 12 | -------------------------------------------------------------------------------- /packages/math/src/sum/index.ts: -------------------------------------------------------------------------------- 1 | import { List, reduce } from '@typed/list' 2 | 3 | import { add } from '../add' 4 | 5 | /** 6 | * Adds together all of the numbers in a list. 7 | * @name sum(number: List): number 8 | */ 9 | export const sum: (numbers: List) => number = reduce(add, 0) 10 | -------------------------------------------------------------------------------- /packages/maybe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/maybe", 3 | "version": "7.0.0", 4 | "description": "Well-typed Maybe data structure", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only maybe", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:browser": "yarn test:unit -- --browser", 14 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 15 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 16 | }, 17 | "dependencies": { 18 | "@typed/functions": "3.0.0" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/TylorS/typed.git" 23 | }, 24 | "keywords": [ 25 | "typed", 26 | "Maybe", 27 | "maybe", 28 | "typescript", 29 | "data-structure" 30 | ], 31 | "author": "Tylor Steinberger ", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/TylorS/typed/issues" 35 | }, 36 | "homepage": "https://github.com/TylorS/typed#readme" 37 | } 38 | -------------------------------------------------------------------------------- /packages/maybe/src/Just.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A JSON-serializable Just data-structure 3 | * @name Just 4 | * @type 5 | */ 6 | export interface Just { 7 | readonly '@typed/Just': A 8 | } 9 | 10 | export namespace Just { 11 | /** 12 | * Creates a Just given a value. 13 | * @name Just.of(value: A): Just 14 | */ 15 | export function of(value: A): Just { 16 | return { '@typed/Just': value } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/maybe/src/Maybe.ts: -------------------------------------------------------------------------------- 1 | import { Just } from './Just' 2 | import { Nothing } from './Nothing' 3 | import { toMaybe } from './toMaybe' 4 | 5 | export type Maybe = Just | Nothing 6 | 7 | export namespace Maybe { 8 | /** 9 | * Creates a Maybe containing a value. If the value is `undefined` or `null` 10 | * a `Nothing` will be returned. All other values will be wrapped in a `Just`. 11 | * @name Maybe.of(value: A): Maybe 12 | */ 13 | export const of: (value: A | void) => Maybe = toMaybe 14 | } 15 | -------------------------------------------------------------------------------- /packages/maybe/src/Nothing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The Nothing type, used in place of nulls or undefined. 3 | * @name Nothing 4 | * @type 5 | */ 6 | export interface Nothing { 7 | readonly '@typed/Nothing': true 8 | } 9 | export const Nothing: Nothing = { '@typed/Nothing': true } 10 | -------------------------------------------------------------------------------- /packages/maybe/src/ap.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from './Maybe' 2 | import { chain } from './chain' 3 | import { curry2 } from '@typed/functions' 4 | import { map } from './map' 5 | 6 | /** 7 | * Applies the function contained in a `Maybe` to the value contained in a 8 | * second `Maybe`. 9 | * @name ap(fn: Maybe<(value: A) => B>, value: Maybe): Maybe 10 | */ 11 | export const ap: MaybeAp = curry2(__ap) 12 | 13 | function __ap(fn: Maybe<(value: A) => B>, maybe: Maybe): Maybe { 14 | return chain(f => map(f, maybe), fn) 15 | } 16 | 17 | export interface MaybeAp { 18 | (fn: Maybe<(value: A) => B>, value: Maybe): Maybe 19 | (fn: Maybe<(value: A) => B>): (value: Maybe) => Maybe 20 | } 21 | -------------------------------------------------------------------------------- /packages/maybe/src/chain.test.ts: -------------------------------------------------------------------------------- 1 | import { Maybe, Nothing, chain, isNothing } from './' 2 | import { Test, describe, given, it } from '@typed/test' 3 | 4 | export const test: Test = describe(`chain`, [ 5 | given(`(a -> Maybe b) and Nothing`, [ 6 | it(`returns Nothing and does not call function`, ({ equal, ok }) => { 7 | let callCount = 0 8 | const f = () => { 9 | ++callCount 10 | return Nothing 11 | } 12 | 13 | const expectedCallCount = 0 14 | 15 | ok(isNothing(chain(f, Nothing))) 16 | equal(expectedCallCount, callCount) 17 | }), 18 | ]), 19 | 20 | given(`(a -> Maybe b) and Just a`, [ 21 | it(`returns result of (a -> Maybe b)`, ({ equal, ok }) => { 22 | let callCount = 0 23 | const f = () => { 24 | ++callCount 25 | return Nothing 26 | } 27 | 28 | const expectedCallCount = 1 29 | 30 | ok(isNothing(chain(f, Maybe.of(1)))) 31 | equal(expectedCallCount, callCount) 32 | }), 33 | ]), 34 | ]) 35 | -------------------------------------------------------------------------------- /packages/maybe/src/chain.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from './Maybe' 2 | import { curry2 } from '@typed/functions' 3 | import { fromJust } from './fromJust' 4 | import { isNothing } from './isNothing' 5 | 6 | /** 7 | * Maps a `Maybe` to another `Maybe`. 8 | * @name chain(f: (value: A) => Maybe, maybe: Maybe): Maybe 9 | */ 10 | export const chain: MaybeChain = curry2(__chain) 11 | 12 | function __chain(f: (value: A) => Maybe, maybe: Maybe): Maybe { 13 | return isNothing(maybe) ? maybe : f(fromJust(maybe)) 14 | } 15 | 16 | export interface MaybeChain { 17 | (f: (value: A) => Maybe, maybe: Maybe): Maybe 18 | (f: (value: A) => Maybe): (maybe: Maybe) => Maybe 19 | } 20 | -------------------------------------------------------------------------------- /packages/maybe/src/combine.test.ts: -------------------------------------------------------------------------------- 1 | import { Just, fromJust, isJust } from './' 2 | import { Test, describe, given, it } from '@typed/test' 3 | 4 | import { combine } from './combine' 5 | 6 | export const test: Test = describe(`combine`, [ 7 | given(`(a -> b -> c) -> Just a -> Just b`, [ 8 | it(`returns Just c`, ({ equal }) => { 9 | const a = Just.of(1) 10 | const b = Just.of(2) 11 | 12 | const f = (a: number, b: number) => a + b 13 | 14 | const c = combine(f, a, b) 15 | const expected = 3 16 | 17 | if (isJust(c)) equal(expected, fromJust(c)) 18 | }), 19 | ]), 20 | ]) 21 | -------------------------------------------------------------------------------- /packages/maybe/src/combine.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from './Maybe' 2 | import { combineArray } from './combineArray' 3 | import { curry3 } from '@typed/functions' 4 | 5 | /** 6 | * Applies a function with the values contained in 2 `Maybes` if both are 7 | * `Just`s. If either `Maybe`s are `Nothing` then `Nothing` is returned. 8 | * @name combine(f: (a: A, b: B) => C, a: Maybe, b: Maybe): Maybe 9 | */ 10 | export const combine: Combine = curry3(__combine) 11 | 12 | export type Combine = { 13 | (f: (valueA: A, valueB: B) => C, maybeA: Maybe, maybeB: Maybe): Maybe 14 | (f: (valueA: A, valueB: B) => C, maybeA: Maybe): (maybeB: Maybe) => Maybe 15 | 16 | (f: (valueA: A, valueB: B) => C): { 17 | (maybeA: Maybe, maybeB: Maybe): Maybe 18 | (maybeA: Maybe): (maybeB: Maybe) => Maybe 19 | } 20 | } 21 | 22 | function __combine( 23 | f: (valueA: A, valueB: B) => C, 24 | maybeA: Maybe, 25 | maybeB: Maybe 26 | ): Maybe { 27 | return combineArray(f, [maybeA, maybeB]) 28 | } 29 | -------------------------------------------------------------------------------- /packages/maybe/src/combineArray.test.ts: -------------------------------------------------------------------------------- 1 | import { Just, Nothing, fromJust, isJust, isNothing } from './' 2 | import { Test, describe, given, it } from '@typed/test' 3 | 4 | import { combineArray } from './combineArray' 5 | 6 | export const test: Test = describe(`combineArray`, [ 7 | given(`(a -> b -> c) -> ( Maybe a, Maybe b, Maybe c )`, [ 8 | it(`returns Maybe d`, ({ equal }) => { 9 | const a = Just.of(1) 10 | const b = Just.of(2) 11 | const c = Just.of(3) 12 | 13 | const f = (x: number, y: number, z: number) => x + y + z 14 | 15 | const d = combineArray(f, [a, b, c]) 16 | 17 | const expected = 6 18 | 19 | if (isJust(d)) equal(expected, fromJust(d)) 20 | }), 21 | ]), 22 | 23 | given(`(a -> b -> c) -> Nothing -> Just b`, [ 24 | it(`does not call supplied function and returns Nothing`, ({ ok }) => { 25 | const a = Nothing 26 | const b = Just.of(1) 27 | 28 | const f = (x: number, y: number) => x + y 29 | 30 | const c = combineArray(f, [a, b]) 31 | 32 | ok(isNothing(c)) 33 | }), 34 | ]), 35 | ]) 36 | -------------------------------------------------------------------------------- /packages/maybe/src/fromJust.ts: -------------------------------------------------------------------------------- 1 | import { Just } from './Just' 2 | 3 | /** 4 | * Extract the value contained in a Just 5 | * @name fromJust(just: Just): A 6 | * @example 7 | * import { fromJust, Just } from '@typed/maybe' 8 | * 9 | * const value = fromJust(Just.of(1)) 10 | * console.log(value) // logs '1' 11 | */ 12 | export function fromJust(just: Just): A { 13 | return just['@typed/Just'] 14 | } 15 | -------------------------------------------------------------------------------- /packages/maybe/src/fromMaybe.test.ts: -------------------------------------------------------------------------------- 1 | import { Maybe, Nothing, fromMaybe } from './' 2 | import { Test, describe, given, it } from '@typed/test' 3 | 4 | export const test: Test = describe(`fromMaybe`, [ 5 | given(`a default value and Nothing`, [ 6 | it(`returns default value`, ({ equal }) => { 7 | const defaultValue = {} 8 | const maybe = Nothing 9 | 10 | equal(defaultValue, fromMaybe(defaultValue, maybe)) 11 | }), 12 | ]), 13 | 14 | given(`a default value and Just a`, [ 15 | it(`returns Just contained value`, ({ equal }) => { 16 | const defaultValue = -1 17 | const value = 1 18 | const maybe = Maybe.of(value) 19 | 20 | equal(value, fromMaybe(defaultValue, maybe)) 21 | }), 22 | ]), 23 | ]) 24 | -------------------------------------------------------------------------------- /packages/maybe/src/fromMaybe.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from './Maybe' 2 | import { curry2 } from '@typed/functions' 3 | import { fromJust } from './fromJust' 4 | import { isJust } from './isJust' 5 | 6 | /** 7 | * Given a default value and a Maybe returns the default value if the Maybe is a 8 | * Nothing or the value contained in a Just. 9 | * @name fromMaybe(defaultValue: A, maybe: Maybe): A 10 | */ 11 | export const fromMaybe: FromMaybe = curry2(__fromMaybe) 12 | 13 | function __fromMaybe(defaultValue: A, maybe: Maybe): A { 14 | return isJust(maybe) ? fromJust(maybe) : defaultValue 15 | } 16 | 17 | export interface FromMaybe { 18 | (defaultValue: A, maybe: Maybe): A 19 | (defaultValue: A): (maybe: Maybe) => A 20 | } 21 | -------------------------------------------------------------------------------- /packages/maybe/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ap' 2 | export * from './chain' 3 | export * from './combine' 4 | export * from './combineArray' 5 | export * from './fromJust' 6 | export * from './fromMaybe' 7 | export * from './isJust' 8 | export * from './isNothing' 9 | export * from './Just' 10 | export * from './map' 11 | export * from './Maybe' 12 | export * from './Nothing' 13 | -------------------------------------------------------------------------------- /packages/maybe/src/isJust.ts: -------------------------------------------------------------------------------- 1 | import { Just } from './Just' 2 | import { Maybe } from './Maybe' 3 | 4 | /** 5 | * Given a Maybe it returns true if the Maybe is Just or 6 | * false if it is a Nothing. 7 | * @name isJust(maybe: Maybe): maybe is Just 8 | * @example 9 | * import { isJust, Nothing, Maybe } from '@typed/maybe' 10 | * 11 | * console.log(isJust(Nothing)) // logs false 12 | * console.log(isJust(Maybe.of(1))) // logs true 13 | */ 14 | export function isJust(maybe: Maybe): maybe is Just { 15 | return maybe.hasOwnProperty('@typed/Just') 16 | } 17 | -------------------------------------------------------------------------------- /packages/maybe/src/isNothing.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from './Maybe' 2 | import { Nothing } from './Nothing' 3 | 4 | /** 5 | * Given a Maybe it returns false if the Maybe is Just or 6 | * true if it is a Nothing. 7 | * @name isNothing(maybe: Maybe): maybe is Nothing 8 | * @example 9 | * import { isNothing, Maybe, Nothing } from '@typed/maybe' 10 | * 11 | * console.log(isNothing(Nothing)) // logs true 12 | * console.log(isNothing(Maybe.of(1))) // logs false 13 | */ 14 | export function isNothing(maybe: Maybe): maybe is Nothing { 15 | return (maybe as Nothing)['@typed/Nothing'] === true 16 | } 17 | -------------------------------------------------------------------------------- /packages/maybe/src/map.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from './Maybe' 2 | import { chain } from './chain' 3 | import { curry2 } from '@typed/functions' 4 | 5 | /** 6 | * Applies a function to the value possibly contained in a `Maybe`. If the 7 | * maybe is a `Nothing` just the `Nothing` is returned. 8 | * @name map(f: (value: A) => B, maybe: Maybe): Maybe 9 | */ 10 | export const map: MaybeMap = curry2(__map) 11 | 12 | function __map(f: (value: A) => B, maybe: Maybe): Maybe { 13 | return chain(a => Maybe.of(f(a)), maybe) 14 | } 15 | 16 | export interface MaybeMap { 17 | (f: (value: A) => B, maybe: Maybe): Maybe 18 | (f: (value: A) => B): (maybe: Maybe) => Maybe 19 | } 20 | -------------------------------------------------------------------------------- /packages/maybe/src/toMaybe.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { fromJust } from './fromJust' 4 | import { isJust } from './isJust' 5 | import { isNothing } from './isNothing' 6 | import { toMaybe } from './toMaybe' 7 | 8 | export const test: Test = describe(`toMaybe`, [ 9 | given(`void`, [ 10 | it(`returns Nothing`, ({ ok }) => { 11 | const value = void 0 12 | const maybe = toMaybe(value) 13 | 14 | ok(isNothing(maybe)) 15 | }), 16 | ]), 17 | 18 | given(`null`, [ 19 | it(`returns Nothing`, ({ ok }) => { 20 | const value = null 21 | const maybe = toMaybe(value) 22 | 23 | ok(isNothing(maybe)) 24 | }), 25 | ]), 26 | 27 | given(`a falsy value`, [ 28 | it(`returns a Just containing the value`, ({ equal }) => { 29 | const value = 0 30 | const maybe = toMaybe(value) 31 | 32 | if (isJust(maybe)) equal(value, fromJust(maybe)) 33 | }), 34 | ]), 35 | ]) 36 | -------------------------------------------------------------------------------- /packages/maybe/src/toMaybe.ts: -------------------------------------------------------------------------------- 1 | import { Just } from './Just' 2 | import { Maybe } from './Maybe' 3 | import { Nothing } from './Nothing' 4 | 5 | export function toMaybe(value: A | void): Maybe { 6 | return value == null ? Nothing : Just.of(value) 7 | } 8 | -------------------------------------------------------------------------------- /packages/objects/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/objects", 3 | "version": "3.0.0", 4 | "description": "Well-typed functions for objects", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only objects", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:browser": "yarn test:unit -- --browser", 14 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 15 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 16 | }, 17 | "dependencies": { 18 | "@typed/functions": "3.0.0", 19 | "@typed/lenses": "3.0.0", 20 | "@typed/logic": "3.0.0", 21 | "@typed/maybe": "7.0.0" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/TylorS/typed.git" 26 | }, 27 | "keywords": [ 28 | "typed", 29 | "objects" 30 | ], 31 | "author": "Tylor Steinberger ", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/TylorS/typed/issues" 35 | }, 36 | "homepage": "https://github.com/TylorS/typed#readme" 37 | } 38 | -------------------------------------------------------------------------------- /packages/objects/src/clone/clone.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { clone } from './clone' 4 | 5 | export const test: Test = describe(`clone`, [ 6 | given(`an object`, [ 7 | it(`returns a cloned object`, ({ equal, notOk }) => { 8 | const obj = { 9 | a: { 10 | b: { 11 | c: { 12 | d: 1, 13 | e: { 14 | f: 2, 15 | }, 16 | }, 17 | }, 18 | }, 19 | } 20 | 21 | const clonedObj = clone(obj) 22 | 23 | equal(clonedObj, obj) 24 | notOk(obj.a === clonedObj.a) 25 | notOk(obj.a.b === clonedObj.a.b) 26 | notOk(obj.a.b.c === clonedObj.a.b.c) 27 | notOk(obj.a.b.c.e === clonedObj.a.b.c.e) 28 | }), 29 | ]), 30 | 31 | given(`an array`, [ 32 | it('returns a cloned array', ({ notOk, equal }) => { 33 | const array = [[1, 2], [2, 3], [[4, 5]]] 34 | 35 | const clonedArray = clone(array) 36 | 37 | equal(array, clonedArray) 38 | notOk(array[0] === clonedArray[0]) 39 | notOk(array[1] === clonedArray[1]) 40 | notOk(array[2] === clonedArray[2]) 41 | notOk(array[2][0] === clonedArray[2][0]) 42 | }), 43 | ]), 44 | ]) 45 | -------------------------------------------------------------------------------- /packages/objects/src/clone/clone.ts: -------------------------------------------------------------------------------- 1 | import { typeOf } from './typeOf' 2 | 3 | /** 4 | * Returns a deep clone of a value. 5 | * @name clone(obj: A): A 6 | */ 7 | export function clone(obj: A): A { 8 | return _clone(obj, [], [], true) 9 | } 10 | 11 | function _clone(value: any, refFrom: Array, refTo: Array, deep: boolean): any { 12 | function copy(copiedValue: any) { 13 | const length = refFrom.length 14 | let i = 0 15 | 16 | for (; i < length; ++i) if (value === refFrom[i]) return refTo[i] 17 | 18 | refFrom[i + 1] = value 19 | refTo[i + 1] = copiedValue 20 | 21 | for (const key in value) { 22 | if (!value.hasOwnProperty(key)) continue 23 | 24 | copiedValue[key] = deep ? _clone(value[key] as any, refFrom, refTo, true) : value[key] 25 | } 26 | 27 | return copiedValue 28 | } 29 | 30 | switch (typeOf(value)) { 31 | case 'Object': 32 | return copy({}) 33 | case 'Array': 34 | return copy([]) 35 | case 'Date': 36 | return new Date(value.valueOf()) 37 | case 'RegExp': 38 | return cloneRegexp(value) 39 | default: 40 | return value 41 | } 42 | } 43 | 44 | function cloneRegexp(pattern: RegExp): RegExp { 45 | return new RegExp( 46 | pattern.source, 47 | (pattern.global ? 'g' : '') + 48 | (pattern.ignoreCase ? 'i' : '') + 49 | (pattern.multiline ? 'm' : '') + 50 | (pattern.sticky ? 'y' : '') + 51 | (pattern.unicode ? 'u' : '') 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /packages/objects/src/clone/index.ts: -------------------------------------------------------------------------------- 1 | export * from './clone' 2 | -------------------------------------------------------------------------------- /packages/objects/src/clone/typeOf/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typeOf' 2 | -------------------------------------------------------------------------------- /packages/objects/src/clone/typeOf/typeOf.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TylorS/typed-unmaintained/ce54f35416a8057d345f4f5ca6caf4c761643176/packages/objects/src/clone/typeOf/typeOf.test.ts -------------------------------------------------------------------------------- /packages/objects/src/clone/typeOf/typeOf.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the type of a value. 3 | * @name typeOf(value: any): string 4 | */ 5 | export function typeOf(value: string): 'String' 6 | export function typeOf(value: number): 'Number' 7 | export function typeOf(value: null): 'Null' 8 | export function typeOf(value: undefined): 'Undefined' 9 | export function typeOf(value: undefined): 'Undefined' 10 | export function typeOf(value: any): string 11 | export function typeOf(value: any): string { 12 | if (value === null) return 'Null' 13 | 14 | if (value === void 0) return `Undefined` 15 | 16 | return Object.prototype.toString.call(value).slice(8, -1) 17 | } 18 | -------------------------------------------------------------------------------- /packages/objects/src/hasOwnProperty/hasOwnProperty.ts: -------------------------------------------------------------------------------- 1 | import { invoker } from '../invoker' 2 | 3 | /** 4 | * Given a property key and an object returns true if that object has a property 5 | * at the given property key. 6 | * @name hasOwnProperty(key: PropertyKey, object: A): boolean 7 | */ 8 | export const hasOwnProperty: HasOwnProperty = invoker( 9 | 1, 10 | 'hasOwnProperty' 11 | ) 12 | 13 | export type HasOwnProperty = { 14 | (key: PropertyKey, object: O): boolean 15 | (key: PropertyKey): (object: O) => boolean 16 | (key: PropertyKey): (object: O) => boolean 17 | } 18 | -------------------------------------------------------------------------------- /packages/objects/src/hasOwnProperty/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hasOwnProperty' 2 | -------------------------------------------------------------------------------- /packages/objects/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | 3 | export * from './clone' 4 | export * from './invoker' 5 | export * from './isEmpty' 6 | export * from './keys' 7 | export * from './length' 8 | export * from './lensPath' 9 | export * from './lensProp' 10 | export * from './path' 11 | export * from './prop' 12 | export * from './set' 13 | export * from './values' 14 | // export last to avoid conflicts with exports native hasOwnProperty 15 | export * from './hasOwnProperty' 16 | -------------------------------------------------------------------------------- /packages/objects/src/invoker/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './invoker' 3 | -------------------------------------------------------------------------------- /packages/objects/src/invoker/invoker.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { invoker } from './invoker' 4 | 5 | export const test: Test = describe(`invoker`, [ 6 | given(`given an arity, and method name`, [ 7 | it(`it returns a curried function that calls a object method `, ({ equal }) => { 8 | const obj = { foo: (a: number, b: number): number => a + b } 9 | 10 | const foo = invoker(2, 'foo') 11 | 12 | equal(obj.foo(1, 2), foo(1)(2)(obj)) 13 | }), 14 | ]), 15 | ]) 16 | -------------------------------------------------------------------------------- /packages/objects/src/invoker/invoker.ts: -------------------------------------------------------------------------------- 1 | import { curry2, curryN } from '@typed/functions' 2 | 3 | import { InvokerFn } from './types' 4 | 5 | /** 6 | * Turns a named method with a specified arity into a function that can be 7 | * called directly supplied with arguments and a target object. The returned 8 | * function is curried and accepts arity + 1 parameters where the final 9 | * parameter is the target object. 10 | * @param arity 11 | * @param method 12 | * @name invoker(arity: number, method: keyof O): (...args, object: O) => A 13 | */ 14 | export const invoker: InvokerFn = (curry2((arity: number, method: keyof O) => 15 | curryN((arity + 1) as any, function(): R { 16 | const target = arguments[arity] 17 | 18 | return target[method].apply(target, Array.prototype.slice.call(arguments, 0, arity)) 19 | }) 20 | ) as any) as InvokerFn 21 | -------------------------------------------------------------------------------- /packages/objects/src/isEmpty/index.ts: -------------------------------------------------------------------------------- 1 | import { always, pipe } from '@typed/functions' 2 | import { equals, ifElse } from '@typed/logic' 3 | 4 | import { keys } from '../keys' 5 | import { length } from '../length' 6 | 7 | /** 8 | * Returns true if an object or array is empty. 9 | * @name isEmpty(obj: A): boolean 10 | * @example 11 | * import { isEmpty } from '167' 12 | * 13 | * isEmpty({}) // true 14 | * isEmpty({ a: 1, b: 2 }) // false 15 | * isEmpty([]) // true 16 | * isEmpty([ 1, 2, 3 ]) // false 17 | * isEmpty(void 0) // false 18 | * isEmpty(null) // false 19 | */ 20 | export const isEmpty: (object: A) => boolean = ifElse( 21 | x => x === null || x === void 0, 22 | always(false), 23 | pipe( 24 | keys, 25 | length, 26 | equals(0) 27 | ) 28 | ) 29 | -------------------------------------------------------------------------------- /packages/objects/src/isEmpty/isEmpty.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { isEmpty } from './' 4 | 5 | export const test: Test = describe(`isEmpty`, [ 6 | given(`null, void, non-empty string, non-empty array, or non-empty object`, [ 7 | it(`returns false`, ({ notOk }) => { 8 | notOk(isEmpty(null)) 9 | notOk(isEmpty(void 0)) 10 | notOk(isEmpty('hello')) 11 | notOk(isEmpty([1, 2, 3])) 12 | notOk(isEmpty({ a: 1, b: 2, c: 3 })) 13 | }), 14 | ]), 15 | 16 | given(`empty string, empty array or empty object`, [ 17 | it(`returns true`, ({ ok }) => { 18 | ok(isEmpty('')) 19 | ok(isEmpty([])) 20 | ok(isEmpty({})) 21 | }), 22 | ]), 23 | ]) 24 | -------------------------------------------------------------------------------- /packages/objects/src/keys/index.ts: -------------------------------------------------------------------------------- 1 | export * from './keys' 2 | -------------------------------------------------------------------------------- /packages/objects/src/keys/keys.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the keys of an object. 3 | * @name keys(obj: A): Array 4 | */ 5 | export const keys = (obj: A): Array => (Object.keys(obj) as any) as Array 6 | -------------------------------------------------------------------------------- /packages/objects/src/length/index.ts: -------------------------------------------------------------------------------- 1 | import { prop } from '../prop' 2 | 3 | /** 4 | * Returns the value of the property `length` 5 | * @name length(obj: { length: A }): A 6 | */ 7 | export const length = prop('length') 8 | -------------------------------------------------------------------------------- /packages/objects/src/lensPath/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lensPath' 2 | export * from './types' 3 | -------------------------------------------------------------------------------- /packages/objects/src/lensPath/lensPath.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | import { fromJust, isJust, map } from '@typed/maybe' 3 | 4 | import { lensPath } from './lensPath' 5 | 6 | export const test: Test = describe(`lensPath`, [ 7 | given(`a path`, [ 8 | it(`returns a Lens`, ({ equal }) => { 9 | const obj = { a: { b: { c: 1 } } } 10 | 11 | const { view, updateAt } = lensPath(['a', 'b', 'c']) 12 | 13 | const increment = updateAt(map(x => x + 1)) 14 | const updatedObj = [0, 1, 2, 3, 4].reduce(increment, obj) 15 | const updatedValue = view(updatedObj) 16 | 17 | const expectedValue = 6 18 | 19 | if (isJust(updatedValue)) equal(expectedValue, fromJust(updatedValue)) 20 | }), 21 | ]), 22 | ]) 23 | -------------------------------------------------------------------------------- /packages/objects/src/lensPath/lensPath.ts: -------------------------------------------------------------------------------- 1 | import { Lens, pipeLenses } from '@typed/lenses' 2 | 3 | import { LensPath } from './types' 4 | import { apply } from '@typed/functions' 5 | import { lensProp } from '../lensProp' 6 | 7 | /** 8 | * Given a path to a value it returns a Lens that operates on that value. 9 | * @name lensPath(path: Array): Lens 10 | */ 11 | export const lensPath: LensPath = function(path: ArrayLike): Lens { 12 | return apply(Array.from(path).map(lensProp), pipeLenses) 13 | } 14 | -------------------------------------------------------------------------------- /packages/objects/src/lensProp/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lensProp' 2 | -------------------------------------------------------------------------------- /packages/objects/src/lensProp/lensProp.ts: -------------------------------------------------------------------------------- 1 | import { Lens, lens } from '@typed/lenses' 2 | 3 | import { prop } from '../prop' 4 | import { set } from '../set' 5 | 6 | /** 7 | * Creates a lens that operates on an object's property. 8 | * @name lensProp(key: K): Lens 9 | */ 10 | export const lensProp = (key: K): Lens => 11 | lens(prop(key), (x, o) => set(key, x, o)) 12 | -------------------------------------------------------------------------------- /packages/objects/src/path/index.ts: -------------------------------------------------------------------------------- 1 | export * from './path' 2 | export * from './types' 3 | -------------------------------------------------------------------------------- /packages/objects/src/path/path.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | import { fromJust, isJust } from '@typed/maybe' 3 | 4 | import { path } from './path' 5 | 6 | export const test: Test = describe(`path`, [ 7 | given(`a path and an object`, [ 8 | it(`returns a Maybe value`, ({ equal }) => { 9 | const value = 1 10 | const obj = { a: value } 11 | 12 | const actual = path(['a'], obj) 13 | 14 | if (isJust(actual)) equal(value, fromJust(actual)) 15 | }), 16 | ]), 17 | ]) 18 | -------------------------------------------------------------------------------- /packages/objects/src/path/path.ts: -------------------------------------------------------------------------------- 1 | import { Path } from './types' 2 | import { curry2 } from '@typed/functions' 3 | import { lensPath } from '../lensPath' 4 | 5 | /** 6 | * Given a path to a value and an object it returns the values contained 7 | * at that path. 8 | * @name path(path: List, obj: A): Maybe 9 | */ 10 | export const path: Path = curry2(function(path: ArrayLike, obj: any): any { 11 | return lensPath(path).view(obj) 12 | }) 13 | -------------------------------------------------------------------------------- /packages/objects/src/prop/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './prop' 3 | -------------------------------------------------------------------------------- /packages/objects/src/prop/prop.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { prop } from './prop' 4 | 5 | export const test: Test = describe(`prop`, [ 6 | given(`K => { K: V }`, [ 7 | it(`returns V`, ({ equal }) => { 8 | equal(1, prop('a', { a: 1 })) 9 | equal(1, prop('a')({ a: 1 })) 10 | equal(1, prop(1, [0, 1])) 11 | }), 12 | ]), 13 | ]) 14 | -------------------------------------------------------------------------------- /packages/objects/src/prop/prop.ts: -------------------------------------------------------------------------------- 1 | import { Prop } from './types' 2 | import { curry2 } from '@typed/functions' 3 | 4 | /** 5 | * Returns the value of a property from an object. 6 | * @name prop(key: K, obj: A): A[K] 7 | */ 8 | export const prop: Prop = curry2((key: K, obj: A): A[K] => obj[key]) 9 | -------------------------------------------------------------------------------- /packages/objects/src/prop/types.ts: -------------------------------------------------------------------------------- 1 | import { StrMap } from '../types' 2 | 3 | export type Prop = { 4 | (prop: number, obj: ArrayLike): A 5 | (prop: K, obj: A): A[K] 6 | 7 | (prop: number): (obj: ArrayLike) => A 8 | (prop: number): (obj: ArrayLike) => A 9 | (prop: K): (obj: A) => A[K] 10 | 11 | (prop: K): (obj: StrMap) => A 12 | } 13 | -------------------------------------------------------------------------------- /packages/objects/src/set/index.ts: -------------------------------------------------------------------------------- 1 | export * from './set' 2 | -------------------------------------------------------------------------------- /packages/objects/src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A StrMap type. Works as a readonly object. 3 | * @name StrMap 4 | * @type 5 | */ 6 | export type StrMap = Readonly> 7 | -------------------------------------------------------------------------------- /packages/objects/src/values/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the values of an object. 3 | * @name values(obj: A): Array 4 | */ 5 | export const values = (obj: A): Array => 6 | Object.keys(obj).map(key => obj[key as keyof A]) 7 | -------------------------------------------------------------------------------- /packages/prelude/src/ap.ts: -------------------------------------------------------------------------------- 1 | import { Arity1, curry2 } from '@typed/functions' 2 | 3 | import { Either } from '@typed/either' 4 | import { List } from '@typed/list' 5 | import { Maybe } from '@typed/maybe' 6 | import { chain } from './chain' 7 | import { map } from './map' 8 | 9 | /** 10 | * Apply the function contained in an Applicative to the values contained 11 | * in another Applicative. Works with all data structures supported by `chain` and 12 | * `map`. 13 | * @name ap(fn: List>, values: List): Array 14 | */ 15 | export const ap: Ap = curry2(__ap) 16 | 17 | function __ap(fn: List>, value: List): Array { 18 | return chain((f: Arity1) => map(f, value), fn) 19 | } 20 | 21 | export type Ap = { 22 | (fn: List>, list: List): Array 23 | (fn: Maybe>, maybe: Maybe): Maybe 24 | (fn: PromiseLike>, promise: PromiseLike): Promise 25 | (fn: Either>, either: Either): Either 26 | 27 | (fn: List>): (list: List) => Array 28 | (fn: Maybe>): (maybe: Maybe) => Maybe 29 | (fn: PromiseLike>): (promise: PromiseLike) => Promise 30 | (fn: Either>): (either: Either) => Either 31 | } 32 | -------------------------------------------------------------------------------- /packages/prelude/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './re-exports' 2 | 3 | export * from './ap' 4 | export * from './chain' 5 | export * from './map' 6 | -------------------------------------------------------------------------------- /packages/prelude/src/map.ts: -------------------------------------------------------------------------------- 1 | import { Either, map as eitherMap, isLeft, isRight } from '@typed/either' 2 | import { List, map as listMap } from '@typed/list' 3 | import { Maybe, isJust, isNothing, map as maybeMap } from '@typed/maybe' 4 | 5 | import { curry2 } from '@typed/functions' 6 | import { isPromiseLike } from '@typed/logic' 7 | 8 | /** 9 | * Map over the value contained in a data structure. 10 | * Works for `List`, `Maybe`, `Either`, and `PromiseLike` data strctures. 11 | * @name map(f: (value: A, index: number) => B, list: List): Array 12 | */ 13 | export const map: Map = curry2(function map(f: (value: any) => any, list: any): any { 14 | if (isJust(list) || isNothing(list)) return maybeMap(f, list) 15 | if (isLeft(list) || isRight(list)) return eitherMap(f, list) 16 | if (isPromiseLike(list)) return Promise.resolve(list.then(f)) 17 | 18 | return listMap(f, list) 19 | }) 20 | 21 | export type Map = { 22 | (f: (value: A, index: number) => B, list: List): Array 23 | (f: (value: A) => B, maybe: Maybe): Maybe 24 | (f: (value: A) => B, promise: PromiseLike): Promise 25 | (f: (value: B) => C, either: Either): Either 26 | 27 | (f: (value: A, index: number) => B): MapArity1 28 | } 29 | 30 | export type MapArity1 = { 31 | (list: List): Array 32 | (maybe: Maybe): Maybe 33 | (promise: Promise): Promise 34 | (either: Either): Either 35 | } 36 | -------------------------------------------------------------------------------- /packages/strings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/strings", 3 | "version": "4.0.0", 4 | "description": "Well-typed functions for strings", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only strings", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:browser": "yarn test:unit -- --browser", 14 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 15 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 16 | }, 17 | "dependencies": { 18 | "@typed/functions": "3.0.0" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/TylorS/typed.git" 23 | }, 24 | "keywords": [ 25 | "typed", 26 | "strings", 27 | "functions", 28 | "fp" 29 | ], 30 | "author": "Tylor Steinberger ", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/TylorS/typed/issues" 34 | }, 35 | "homepage": "https://github.com/TylorS/typed#readme" 36 | } 37 | -------------------------------------------------------------------------------- /packages/strings/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './split' 2 | export * from './substr' 3 | export * from './substring' 4 | export * from './toLowerCase' 5 | export * from './toUpperCase' 6 | export * from './trim' 7 | -------------------------------------------------------------------------------- /packages/strings/src/split.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '@typed/functions' 2 | 3 | /** 4 | * Curried function to call `String.prototype.split` 5 | * @name split(search: string | RegExp, str: string): Array 6 | */ 7 | export const split: Split = curry2(__split) 8 | 9 | export type Split = { 10 | (separator: string | RegExp, str: string): Array 11 | (separator: string | RegExp): (str: string) => Array 12 | } 13 | 14 | function __split(separator: string | RegExp, str: string): Array { 15 | return str.split(separator) 16 | } 17 | -------------------------------------------------------------------------------- /packages/strings/src/substr.ts: -------------------------------------------------------------------------------- 1 | import { curry3 } from '@typed/functions' 2 | 3 | /** 4 | * A curried function to call `String.prototype.substr` 5 | * @name substr(from: number, length: number | undefined, str: string): string 6 | */ 7 | export const substr: Substr = curry3(__substr) 8 | 9 | export type Substr = { 10 | (from: number, length: number | undefined, str: string): string 11 | (from: number, length: number | undefined): (str: string) => string 12 | (from: number): { 13 | (length: number | undefined, str: string): string 14 | (length: number | undefined): (str: string) => string 15 | } 16 | } 17 | 18 | function __substr(from: number, length: number | undefined, str: string): string { 19 | return str.substr(from, length) 20 | } 21 | -------------------------------------------------------------------------------- /packages/strings/src/substring.ts: -------------------------------------------------------------------------------- 1 | import { curry3 } from '@typed/functions' 2 | 3 | /** 4 | * A curried function to call `String.prototype.substring` 5 | * @name substring(from: number, to: number | undefined, str: string) 6 | */ 7 | export const substring: Substring = curry3(__substring) 8 | 9 | export type Substring = { 10 | (from: number, to: number | undefined, str: string): string 11 | (from: number, to: number | undefined): (str: string) => string 12 | (from: number): { 13 | (to: number | undefined, str: string): string 14 | (to: number | undefined): (str: string) => string 15 | } 16 | } 17 | 18 | function __substring(from: number, to: number | undefined, str: string): string { 19 | return str.substring(from, to) 20 | } 21 | -------------------------------------------------------------------------------- /packages/strings/src/toLowerCase.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A function to call `String.prototype.toLowerCase` 3 | * @name toLowerCase(str: string): string 4 | */ 5 | export const toLowerCase = (str: string) => str.toLowerCase() 6 | -------------------------------------------------------------------------------- /packages/strings/src/toUpperCase.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A function to call `String.prototype.toUpperCase` 3 | * @name toUpperCase(str: string): string 4 | */ 5 | export const toUpperCase = (str: string) => str.toUpperCase() 6 | -------------------------------------------------------------------------------- /packages/strings/src/trim.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A function to call `String.prototype.trim` 3 | * @name trim(str: string): string 4 | */ 5 | export const trim = (str: string): string => str.trim() 6 | -------------------------------------------------------------------------------- /packages/uuid/README.md: -------------------------------------------------------------------------------- 1 | # @typed/uuid -- 2.0.0 2 | 3 | Cross-platform TypeScript implementation of UUID 4 | 5 | ## Get it 6 | ```sh 7 | yarn add @typed/uuid 8 | # or 9 | npm install --save @typed/uuid 10 | ``` 11 | 12 | ## API Documentation 13 | 14 | All functions are curried! 15 | 16 | #### Uuid 17 | 18 |

19 | 20 | A Universally Unique identifier. 21 | 22 | **Note:** A Uuid will *not* actually have ._uuid property on it. This is only used to 23 | differentiate type `Uuid` from type `string` for an improved type experience. 24 | 25 |

26 | 27 | 28 | ```typescript 29 | 30 | export type Uuid = string & { readonly _uuid: undefined } 31 | 32 | ``` 33 | 34 | 35 | #### isUuid(value: string): value is Uuid 36 | 37 |

38 | 39 | Returns `true` if a string is a UUID. 40 | 41 |

42 | 43 | 44 |
45 | See the code 46 | 47 | ```typescript 48 | 49 | export function isUuid(value: string | Uuid): value is Uuid { 50 | return uuidPattern.test(value as string) 51 | } 52 | 53 | ``` 54 | 55 |
56 |
57 | 58 | 59 | #### uuid(): Uuid 60 | 61 |

62 | 63 | Creates a universally unique identifier that works in both browser and Node.js 64 | environments. 65 | 66 |

67 | 68 | 69 |
70 | See the code 71 | 72 | ```typescript 73 | 74 | export const uuid = pipe(randomUuidSeed, uuid4) as () => Uuid 75 | 76 | ``` 77 | 78 |
79 |
80 | -------------------------------------------------------------------------------- /packages/uuid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typed/uuid", 3 | "version": "2.0.0", 4 | "description": "Cross-platform TypeScript implementation of UUID", 5 | "main": "lib/index.js", 6 | "module": "lib.es2015/index.js", 7 | "jsnext:main": "lib.es2015/index.js", 8 | "typings": "lib/index.d.ts", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "cd ../../ && node ./tools/build.js --only uuid", 12 | "test": "yarn test:lint && yarn test:unit", 13 | "test:unit": "../../node_modules/.bin/typed-test 'src/*.test.ts' 'src/**/*.test.ts'", 14 | "test:lint": "../../node_modules/.bin/prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote --trailing-comma es5 --parser typescript src/**/*.ts" 15 | }, 16 | "dependencies": { 17 | "@typed/functions": "3.0.0", 18 | "@typed/logic": "3.0.0" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/TylorS/typed.git" 23 | }, 24 | "keywords": [ 25 | "typed", 26 | "typescript", 27 | "uuid", 28 | "browser", 29 | "node", 30 | "cross-platform" 31 | ], 32 | "author": "Tylor Steinberger ", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/TylorS/typed/issues" 36 | }, 37 | "homepage": "https://github.com/TylorS/typed#readme", 38 | "devDependencies": { 39 | "@types/node": "8.10.48" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/uuid/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './uuid' 3 | export * from './isUuid' 4 | -------------------------------------------------------------------------------- /packages/uuid/src/isUuid.ts: -------------------------------------------------------------------------------- 1 | import { Uuid } from './types' 2 | 3 | const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ 4 | 5 | /** 6 | * Returns `true` if a string is a UUID. 7 | * @name isUuid(value: string): value is Uuid 8 | */ 9 | export function isUuid(value: string | Uuid): value is Uuid { 10 | return uuidPattern.test(value as string) 11 | } 12 | -------------------------------------------------------------------------------- /packages/uuid/src/randomUuidSeed/RandomNumberGenerator/BrowserGenerator.ts: -------------------------------------------------------------------------------- 1 | import { RandomNumberGenerator } from './types' 2 | import { UuidArray } from '../types' 3 | import { VALID_UUID_LENGTH } from './constants' 4 | 5 | export class BrowserGenerator implements RandomNumberGenerator { 6 | private browserCrypto: Crypto 7 | constructor() { 8 | this.browserCrypto = crypto || msCrypto 9 | } 10 | 11 | randomUuidSeed = (): UuidArray => 12 | (this.browserCrypto.getRandomValues(new Uint8Array(VALID_UUID_LENGTH)) as any) as UuidArray 13 | } 14 | 15 | declare global { 16 | // Adds support for IE 11. 17 | export const msCrypto: Crypto 18 | } 19 | -------------------------------------------------------------------------------- /packages/uuid/src/randomUuidSeed/RandomNumberGenerator/NodeGenerator.ts: -------------------------------------------------------------------------------- 1 | import { RandomNumberGenerator } from './types' 2 | import { UuidArray } from '../types' 3 | import { VALID_UUID_LENGTH } from './constants' 4 | 5 | export class NodeGenerator implements RandomNumberGenerator { 6 | private nodeCrypto: NodeCrypto 7 | constructor() { 8 | this.nodeCrypto = require('crypto') 9 | } 10 | 11 | randomUuidSeed = (): UuidArray => this.nodeCrypto.randomBytes(VALID_UUID_LENGTH) 12 | } 13 | 14 | type NodeCrypto = { 15 | randomBytes(length: typeof VALID_UUID_LENGTH): UuidArray 16 | } 17 | -------------------------------------------------------------------------------- /packages/uuid/src/randomUuidSeed/RandomNumberGenerator/constants.ts: -------------------------------------------------------------------------------- 1 | export const VALID_UUID_LENGTH: 16 = 16 2 | -------------------------------------------------------------------------------- /packages/uuid/src/randomUuidSeed/RandomNumberGenerator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './BrowserGenerator' 3 | export * from './NodeGenerator' 4 | -------------------------------------------------------------------------------- /packages/uuid/src/randomUuidSeed/RandomNumberGenerator/types.ts: -------------------------------------------------------------------------------- 1 | import { UuidArray } from '../types' 2 | 3 | export interface RandomNumberGenerator { 4 | readonly randomUuidSeed: () => UuidArray 5 | } 6 | -------------------------------------------------------------------------------- /packages/uuid/src/randomUuidSeed/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './randomUuidSeed' 3 | -------------------------------------------------------------------------------- /packages/uuid/src/randomUuidSeed/isBrowser.ts: -------------------------------------------------------------------------------- 1 | export const isBrowser: boolean = typeof crypto !== 'undefined' || typeof msCrypto !== 'undefined' 2 | -------------------------------------------------------------------------------- /packages/uuid/src/randomUuidSeed/randomUuidSeed.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, it } from '@typed/test' 2 | 3 | import { randomUuidSeed } from './randomUuidSeed' 4 | 5 | export const test: Test = describe(`randomUuidSeed`, [ 6 | it(`returns an object with keys numbered 0-15 with values of type number`, ({ equal }) => { 7 | const sut = randomUuidSeed() 8 | const typeOf =
(type: string) => (value: A) => equal(type, typeof value) 9 | const isNumber = typeOf('number') 10 | 11 | isNumber(sut[0]) 12 | isNumber(sut[1]) 13 | isNumber(sut[2]) 14 | isNumber(sut[3]) 15 | isNumber(sut[4]) 16 | isNumber(sut[5]) 17 | isNumber(sut[6]) 18 | isNumber(sut[7]) 19 | isNumber(sut[8]) 20 | isNumber(sut[9]) 21 | isNumber(sut[10]) 22 | isNumber(sut[11]) 23 | isNumber(sut[12]) 24 | isNumber(sut[13]) 25 | isNumber(sut[14]) 26 | isNumber(sut[15]) 27 | }), 28 | ]) 29 | -------------------------------------------------------------------------------- /packages/uuid/src/randomUuidSeed/randomUuidSeed.ts: -------------------------------------------------------------------------------- 1 | import { BrowserGenerator, NodeGenerator, RandomNumberGenerator } from './RandomNumberGenerator' 2 | 3 | import { isBrowser } from './isBrowser' 4 | 5 | export const { randomUuidSeed }: RandomNumberGenerator = isBrowser 6 | ? new BrowserGenerator() 7 | : new NodeGenerator() 8 | -------------------------------------------------------------------------------- /packages/uuid/src/randomUuidSeed/types.ts: -------------------------------------------------------------------------------- 1 | export type UuidArray = Readonly<{ 2 | 0: number 3 | 1: number 4 | 2: number 5 | 3: number 6 | 4: number 7 | 5: number 8 | 6: number 9 | 7: number 10 | 8: number 11 | 9: number 12 | 10: number 13 | 11: number 14 | 12: number 15 | 13: number 16 | 14: number 17 | 15: number 18 | }> 19 | -------------------------------------------------------------------------------- /packages/uuid/src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A Universally Unique identifier. 3 | * 4 | * **Note:** A Uuid will *not* actually have ._uuid property on it. This is only used to 5 | * differentiate type `Uuid` from type `string` for an improved type experience. 6 | * @name Uuid 7 | * @type 8 | */ 9 | export type Uuid = string & { readonly _uuid: undefined } 10 | -------------------------------------------------------------------------------- /packages/uuid/src/uuid.ts: -------------------------------------------------------------------------------- 1 | import { Uuid } from './types' 2 | import { pipe } from '@typed/functions' 3 | import { randomUuidSeed } from './randomUuidSeed' 4 | import { uuid4 } from './uuid4' 5 | 6 | /** 7 | * Creates a universally unique identifier that works in both browser and Node.js 8 | * environments. 9 | * @name uuid(): Uuid 10 | */ 11 | export const uuid = pipe(randomUuidSeed, uuid4) as () => Uuid 12 | -------------------------------------------------------------------------------- /packages/uuid/src/uuid4/index.ts: -------------------------------------------------------------------------------- 1 | export * from './uuid4' 2 | -------------------------------------------------------------------------------- /packages/uuid/src/uuid4/uuid.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, describe, given, it } from '@typed/test' 2 | 3 | import { Uuid } from '../types' 4 | import { UuidArray } from '../randomUuidSeed' 5 | import { uuid4 } from './uuid4' 6 | 7 | export const test: Test = describe(`uuid4`, [ 8 | given(`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]`, [ 9 | it(`returns`, ({ equal }) => { 10 | const expected = '1234-56-478-89a-bcdef10' as Uuid 11 | 12 | equal(expected, uuid4(createUuidArray())) 13 | }), 14 | ]), 15 | ]) 16 | 17 | function createUuidArray(): UuidArray { 18 | return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as UuidArray 19 | } 20 | -------------------------------------------------------------------------------- /packages/uuid/src/uuid4/uuid4.ts: -------------------------------------------------------------------------------- 1 | import { Uuid } from '../types' 2 | import { UuidArray } from '../randomUuidSeed' 3 | 4 | export function uuid4(randomNumbers: UuidArray): Uuid { 5 | return ((randomNumbers[0].toString(16) + 6 | randomNumbers[1].toString(16) + 7 | randomNumbers[2].toString(16) + 8 | randomNumbers[3].toString(16) + 9 | '-' + 10 | randomNumbers[4].toString(16) + 11 | randomNumbers[5].toString(16) + 12 | '-' + 13 | ((randomNumbers[6] & 0x0f) | 0x40).toString(16) + 14 | randomNumbers[7].toString(16) + 15 | '-' + 16 | ((randomNumbers[8] & 0xbf) | 0x80).toString(16) + 17 | randomNumbers[9].toString(16) + 18 | '-' + 19 | randomNumbers[10].toString(16) + 20 | randomNumbers[11].toString(16) + 21 | randomNumbers[12].toString(16) + 22 | randomNumbers[13].toString(16) + 23 | randomNumbers[14].toString(16) + 24 | randomNumbers[15].toString(16)) as any) as Uuid 25 | } 26 | -------------------------------------------------------------------------------- /packages/uuid/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@8.10.48": 6 | version "8.10.48" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.48.tgz#e385073561643a9ba6199a1985ffc03530f90781" 8 | integrity sha512-c35YEBTkL4rzXY2ucpSKy+UYHjUBIIkuJbWYbsGIrKLEWU5dgJMmLkkIb3qeC3O3Tpb1ZQCwecscvJTDjDjkRw== 9 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "pinVersions": true, 3 | "semanticCommits": true, 4 | "depTypes": [{"depType": "dependencies", "pinVersions": false}] 5 | } 6 | -------------------------------------------------------------------------------- /tools/build.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | const { readdirSync } = require('fs') 3 | 4 | const { buildPackage } = require('./build/tsc') 5 | 6 | const ROOT_DIRECTORY = join(__dirname, '..') 7 | const PACKAGES_DIRECTORY = join(ROOT_DIRECTORY, 'packages') 8 | 9 | let allPackages = [ 10 | 'functions', 11 | 'maybe', 12 | 'either', 13 | 'lenses', 14 | 'logic', 15 | 'list', 16 | 'math', 17 | 'objects', 18 | 'strings', 19 | 'future', 20 | 'prelude', 21 | 'uuid' 22 | ] 23 | 24 | if (readdirSync(PACKAGES_DIRECTORY).length > allPackages.length) { 25 | throw new Error('Please update list of packages in build script') 26 | } 27 | 28 | console.log() // used to add separation between commands 29 | 30 | const args = process.argv.slice(2) 31 | 32 | const onlyIndex = 33 | args.indexOf('--only') > -1 34 | ? args.indexOf('--only') + 1 35 | : args.indexOf('-o') > -1 ? args.indexOf('-o') + 1 : -1 36 | 37 | if (onlyIndex > -1) { 38 | const only = args[onlyIndex] 39 | 40 | allPackages = allPackages.filter(name => name === only) 41 | } 42 | 43 | let promise = Promise.resolve() 44 | for (const pkg of allPackages) { 45 | const packageDirectory = join(PACKAGES_DIRECTORY, pkg) 46 | 47 | promise = promise.then(() => buildPackage(packageDirectory)) 48 | } 49 | 50 | promise 51 | .then(() => { 52 | console.log(`\nDONE!`) 53 | }) 54 | .catch(err => { 55 | console.error(err) 56 | 57 | process.exit(1) 58 | }) 59 | -------------------------------------------------------------------------------- /tools/docs.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | const { readdirSync } = require('fs') 3 | 4 | const { generateDocs } = require('./docs/generateDocs') 5 | 6 | const ROOT_DIRECTORY = join(__dirname, '..') 7 | const PACKAGES_DIRECTORY = join(ROOT_DIRECTORY, 'packages') 8 | 9 | let allPackages = readdirSync(PACKAGES_DIRECTORY) 10 | 11 | console.log() // used to add separation between commands 12 | 13 | const args = process.argv.slice(2) 14 | 15 | const onlyIndex = args.indexOf('--only') > -1 16 | ? args.indexOf('--only') + 1 17 | : args.indexOf('-o') > -1 ? args.indexOf('-o') + 1 : -1 18 | 19 | if (onlyIndex > -1) { 20 | const only = args[onlyIndex] 21 | 22 | allPackages = allPackages.filter((name) => name.indexOf(only) > -1) 23 | } 24 | 25 | let promise = Promise.resolve() 26 | for (const pkg of allPackages) { 27 | const packageDirectory = join(PACKAGES_DIRECTORY, pkg) 28 | 29 | promise = promise.then(() => generateDocs(packageDirectory)) 30 | } 31 | 32 | promise 33 | .then(() => { 34 | console.log(`\nDONE!`) 35 | }) 36 | .catch(err => { 37 | console.error(err) 38 | 39 | process.exit(1) 40 | }) 41 | -------------------------------------------------------------------------------- /tools/docs/findFiles.js: -------------------------------------------------------------------------------- 1 | const expand = require('glob-expand') 2 | const path = require('path') 3 | 4 | const PATTERNS = ['src/*/*.ts', 'src/**/*.ts', '!src/*.test.ts', '!src/**/*.test.ts'] 5 | 6 | exports.findFiles = findFiles 7 | 8 | function findFiles(cwd) { 9 | return expand({ cwd, filter: 'isFile' }, PATTERNS).map(file => path.join(cwd, file)) 10 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "es5", 6 | "es2015" 7 | ], 8 | "target": "es5", 9 | "moduleResolution": "node", 10 | "module": "commonjs", 11 | "declaration": true, 12 | "noImplicitAny": true, 13 | "sourceMap": true, 14 | "noUnusedParameters": true, 15 | "noUnusedLocals": true, 16 | "strictNullChecks": true, 17 | "strict": true 18 | }, 19 | "include": [ 20 | "packages/*/src/**/*.ts", 21 | "tools/*/src/**/*.ts" 22 | ] 23 | } 24 | --------------------------------------------------------------------------------