Added nop, identity, partial, cmp

This commit is contained in:
Michaël Lemaire 2019-09-16 20:20:42 +02:00
commit 5f2a1a0f4b
10 changed files with 10296 additions and 0 deletions

10
.editorconfig Normal file
View file

@ -0,0 +1,10 @@
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
max_line_length = off

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.venv
node_modules
.rts2_cache_*
.coverage
/dist/

42
README.md Normal file
View file

@ -0,0 +1,42 @@
tk-functional
=============
About
-----
Provides some common helpers for functional-style programming.
Typescript definitions are included.
Issues can be reported on [GitLab](https://gitlab.com/thunderk/tk-serializer/issues).
Functions
---------
**nop** does nothing (useful for some callbacks):
```typescript
new ConstructorWithMandatoryCallback(nop)
```
**identity** returns its argument untouched:
```typescript
a === identity(a) // => true
```
**partial** applies a partial configuration object as first argument of compatible functions:
```typescript
const sum = (args: {a: number, b: number}) => args.a + args.b
const plus1 = partial({a: 1}, sum);
plus1({b: 8}) // => 9
```
**cmp** simplifies the use of array sorting:
```typescript
[8, 3, 5].sort(cmp()) // => [3, 5, 8]
[8, 3, 5].sort(cmp({ reverse: true })) // => [8, 5, 3]
[-2, 8, -7].sort(cmp({ key: Math.abs })) // => [-2, -7, 8]
```

16
activate_node Normal file
View file

@ -0,0 +1,16 @@
# Activation script for virtual nodejs environment
# Usage:
# source activate_node
vdir="./.venv"
expected="10.16.3"
if [ \! -f "./activate_node" ]
then
echo "Not in project directory"
exit 1
fi
test -x "${vdir}/bin/nodeenv" || ( python3 -m venv "${vdir}" && "${vdir}/bin/pip" install --upgrade nodeenv )
test "$(${vdir}/node/bin/nodejs --version)" = "v${expected}" || "${vdir}/bin/nodeenv" --node=${expected} --force "${vdir}/node"
source "${vdir}/node/bin/activate"

23
jest.config.js Normal file
View file

@ -0,0 +1,23 @@
module.exports = {
transform: {
"^.+\\.ts$": "ts-jest"
},
moduleFileExtensions: [
"ts",
"js",
"json",
"node"
],
restoreMocks: true,
collectCoverage: true,
collectCoverageFrom: [
"src/**/*.ts",
"!src/**/*.test.ts",
],
coverageDirectory: ".coverage",
coverageReporters: [
"lcovonly",
"html",
"text-summary"
]
}

10080
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

29
package.json Normal file
View file

@ -0,0 +1,29 @@
{
"name": "tk-functional",
"version": "1.0.0",
"description": "Helpers for functional programming style",
"main": "dist/tk-functional.js",
"scripts": {
"test": "npx tk-base test",
"normalize": "npx tk-base normalize",
"build": "npx tk-base build",
"prepare": "npm run build",
"prepublishOnly": "npm test"
},
"author": {
"name": "Michaël Lemaire",
"url": "https://thunderk.net"
},
"license": "ISC",
"devDependencies": {
"tk-base": "^0.1.2"
},
"source": "src/index.ts",
"umd:main": "dist/tk-functional.js",
"types": "dist/src/index.d.ts",
"files": [
"/src",
"/dist"
],
"dependencies": {}
}

37
src/index.test.ts Normal file
View file

@ -0,0 +1,37 @@
import { partial, cmp, nop, identity } from "./index";
describe("nop", () => {
it("does nothing", () => {
expect(nop()).toBeUndefined();
});
});
describe("identity", () => {
it("returns the argument untouched", () => {
expect(identity(5)).toBe(5);
expect(identity(null)).toBe(null);
});
});
describe("partial", () => {
it("applies systematically fixed configuration", () => {
const func = (conf: { a: number, b: string, c: number }) => `${conf.a}${conf.b}${conf.c}`;
const pfunc = partial({ b: "test" }, func);
expect(pfunc({ a: 5, c: 8 })).toEqual("5test8");
});
it("pass through remaining arguments", () => {
const func = (conf: { a: number, b: string }, c: number) => `${conf.a}${conf.b}${c}`;
const pfunc = partial({ b: "test" }, func);
expect(pfunc({ a: 2 }, 3)).toEqual("2test3");
});
});
describe("cmp", () => {
it("simplifies array sorting", () => {
expect([8, 3, 5].sort(cmp())).toEqual([3, 5, 8]);
expect([8, 3, 5, 8].sort(cmp({ reverse: true }))).toEqual([8, 8, 5, 3]);
expect([-2, 8, -7].sort(cmp({ key: Math.abs }))).toEqual([-2, -7, 8]);
expect(["c", "a", "b"].sort(cmp())).toEqual(["a", "b", "c"]);
});
});

39
src/index.ts Normal file
View file

@ -0,0 +1,39 @@
/**
* Functions that does nothing (useful for default callbacks)
*/
export function nop(...args: any[]): any {
}
/**
* Identity function (returns the sole argument untouched)
*/
export function identity<T>(input: T): T {
return input;
}
/**
* Partially apply fixed arguments to a function with named arguments
*/
export function partial<A extends object, PA extends Partial<A>, RA extends any[], R>(fixedargs: PA, func: (args: A, ...rest: RA) => R): (args: Omit<A, keyof PA>, ...rest: RA) => R {
return (args: Omit<A, keyof PA>, ...rest: RA) => func({ ...fixedargs, ...args } as any, ...rest);
}
export type cmpArgs = Readonly<{ key: (item: any) => any, reverse: boolean }>
export const cmpDefaults: cmpArgs = { key: identity, reverse: false }
/**
* Compare operator, that can be used in sort() calls.
*/
export function cmp(args?: Partial<cmpArgs>): (a: any, b: any) => number {
const fargs = { ...cmpDefaults, ...args };
return (a, b) => {
const ka = fargs.key(a);
const kb = fargs.key(b);
if (ka > kb) {
return fargs.reverse ? -1 : 1;
} else if (ka < kb) {
return fargs.reverse ? 1 : -1;
} else {
return 0;
}
}
}

15
tsconfig.json Normal file
View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"preserveConstEnums": true,
"strict": true,
"declaration": true,
"esModuleInterop": true,
"lib": [
"dom",
"es6"
],
"target": "es6"
}
}