1
0
Fork 0

Switched to deno

This commit is contained in:
Michaël Lemaire 2020-05-12 00:34:57 +02:00
parent eea2d45951
commit 613b92adbd
25 changed files with 493 additions and 9458 deletions

View File

@ -1,10 +1,9 @@
root = true
[*]
[*.ts]
charset = utf-8
end_of_line = lf
insert_final_newline = 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

6
.gitignore vendored
View File

@ -1,5 +1 @@
.venv
node_modules
.rts2_cache_*
.coverage
/dist/
deno.d.ts

View File

@ -1,11 +0,0 @@
image: node:latest
cache:
paths:
- node_modules/
test:
before_script:
- npm install
script:
- npm test

View File

@ -1,3 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"deno.enable": true
}

161
README.md
View File

@ -1,161 +0,0 @@
tk-functional
=============
[![pipeline status](https://gitlab.com/thunderk/tk-functional/badges/master/pipeline.svg)](https://gitlab.com/thunderk/tk-functional/commits/master)
[![coverage report](https://gitlab.com/thunderk/tk-functional/badges/master/coverage.svg)](https://gitlab.com/thunderk/tk-functional/commits/master)
[![npm version](https://img.shields.io/npm/v/tk-functional.svg)](https://npmjs.com/tk-functional)
[![npm size](https://img.shields.io/bundlephobia/min/tk-functional.svg)](https://bundlephobia.com/result?p=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-functional/issues).
Functions
---------
**always** and **never** return true and false respectively
```typescript
always() // => true
never() // => false
```
**and**, **or** and **not** allow to combine or negate predicates
```typescript
const a = and((x: number) => x > 2, (x: number) => x < 5);
a(2) // => false
a(3) // => true
a(4) // => true
a(5) // => false
const o = or((x: number) => x < 2, (x: number) => x > 3);
o(1) // => true
o(2) // => false
o(3) // => false
o(4) // => true
const n = not((x: number) => x == 1);
n(0) // => true
n(1) // => false
n(2) // => true
```
**at** gets an array's item by position:
```typescript
const getthird = at(2);
getthird([2, 4, 8, 16]); // => 8
getthird([2, 4]); // => undefined
const getsecondlast = at(-2);
getsecondlast([1, 2, 3, 4]); // => 3
getsecondlast([1]); // => undefined
```
**attr** gets an object's attribute:
```typescript
const getx = attr("x");
getx({x: 3}); // => 3
```
**bool** checks for boolean equivalence (in a broader sense than !(!(val))):
```typescript
bool(undefined); // => false
bool(null); // => false
bool(-1); // => true
bool(0); // => false
bool(1); // => true
bool(""); // => false
bool(" "); // => true
bool("abc"); // => true
bool([]); // => false
bool([1, 2, 3]); // => true
bool({}); // => false
bool({x: 1}); // => true
```
**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]
```
**first** and **last** return the first or last item of an array:
```typescript
first([1, 2, 3]) // => 1
first([]) // => undefined
last([1, 2, 3]) // => 3
last([]) // => undefined
```
**identity** returns its argument untouched:
```typescript
a === identity(a) // => true
```
**is** and **isinstance** checks for strict equality and inheritance:
```typescript
const f = is(8);
f(8) // => true
f(5 + 3) // => true
f("8") // => false
f(null) // => false
class A { }
class A1 extends A { }
class A2 extends A { }
class B { }
const f: any[] = [5, null, undefined, new A, new A1, new B, new A2];
const result: A[] = f.filter(isinstance(A)); // => [f[3], f[4], f[6]]
```
**nn**, **nu** and **nnu** checks at run-time for null or undefined:
```typescript
nn(undefined) // => undefined
nn(null) // => Error
nn(1) // => 1
nu(undefined) // => Error
nu(null) // => null
nu(1) // => 1
nnu(undefined) // => Error
nnu(null) // => Error
nnu(1) // => 1
```
**nop** does nothing (useful for some callbacks):
```typescript
new ConstructorWithMandatoryCallback(nop)
```
**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
```
**pipe** chains two functions as one:
```typescript
const f = pipe((x: number) => x * 2, (x: number) => x + 1);
f(3) // => 7 ((3 * 2) + 1)
```

View File

@ -1,16 +0,0 @@
# 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"

6
all.ts Normal file
View File

@ -0,0 +1,6 @@
export * from "./comparison.ts";
export * from "./composition.ts";
export * from "./iterables.ts";
export * from "./objects.ts";
export * from "./predicates.ts";
export * from "./typing.ts";

58
comparison.test.ts Normal file
View File

@ -0,0 +1,58 @@
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { bool, cmp, is } from "./comparison.ts";
Deno.test("cmp", () => {
assertEquals([8, 3, 5].sort(cmp()), [3, 5, 8]);
assertEquals([8, 3, 5, 8].sort(cmp({ reverse: true })), [8, 8, 5, 3]);
assertEquals([-2, 8, -7].sort(cmp({ key: Math.abs })), [-2, -7, 8]);
assertEquals(["c", "a", "b"].sort(cmp()), ["a", "b", "c"]);
});
Deno.test("is", () => {
const f1 = is(5);
assertEquals(f1(5), true);
assertEquals(f1(4), false);
assertEquals(f1("a" as any), false);
assertEquals(f1(null as any), false);
assertEquals(f1(undefined as any), false);
const obj: { x: number } = { x: 1 };
let f2 = is(obj);
assertEquals(f2(obj), true);
assertEquals(f2({ x: 1 }), false);
assertEquals(f2({ y: 1 } as any), false);
});
Deno.test("bool", () => {
assertEquals(bool(null), false);
assertEquals(bool(undefined), false);
assertEquals(bool(false), false);
assertEquals(bool(true), true);
assertEquals(bool(-1), true);
assertEquals(bool(0), false);
assertEquals(bool(1), true);
assertEquals(bool(""), false);
assertEquals(bool(" "), true);
assertEquals(bool("abc"), true);
assertEquals(bool([]), false);
assertEquals(bool([0]), true);
assertEquals(bool([1, 2, 3]), true);
assertEquals(bool(new Set()), false);
assertEquals(bool(new Set([0])), true);
assertEquals(bool(new Set([1, 2, 3])), true);
assertEquals(bool({}), false);
assertEquals(bool({ a: 5 }), true);
class Obj1 {}
class Obj2 {
private x = 0;
}
assertEquals(bool(new Obj1()), false);
assertEquals(bool(new Obj2()), true);
});

46
comparison.ts Normal file
View File

@ -0,0 +1,46 @@
import { identity } from "./composition.ts";
type cmpArgs = Readonly<{ key: (item: any) => any; reverse: boolean }>;
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;
}
};
}
/**
* Get a function to check the strict equality with a reference
*/
export function is<T, S extends T>(
ref: T,
): (input: S) => input is Extract<T, S> {
return (x): x is Extract<T, S> => x === ref;
}
/**
* Convert a value to a boolean (are considered falsy: 0, false, "", {}, [], null, undefined)
*/
export function bool<T>(value: T | null | undefined): value is T;
export function bool(value: any): boolean {
if (!value) {
return false;
} else if (value instanceof Set) {
return value.size > 0;
} else if (typeof value == "object") {
return Object.keys(value).length > 0;
} else {
return true;
}
}

30
composition.test.ts Normal file
View File

@ -0,0 +1,30 @@
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { identity, nop, partial, pipe } from "./composition.ts";
Deno.test("nop", () => {
assertEquals(nop(), undefined);
});
Deno.test("identity", () => {
assertEquals(identity(5), 5);
assertEquals(identity(null), null);
});
Deno.test("partial", () => {
const func1 = (conf: { a: number; b: string; c: number }) =>
`${conf.a}${conf.b}${conf.c}`;
const pfunc1 = partial({ b: "test" }, func1);
assertEquals(pfunc1({ a: 5, c: 8 }), "5test8");
const func2 = (conf: { a: number; b: string }, c: number) =>
`${conf.a}${conf.b}${c}`;
const pfunc2 = partial({ b: "test" }, func2);
assertEquals(pfunc2({ a: 2 }, 3), "2test3");
});
Deno.test("pipe", () => {
const f = pipe((x: number) => x * 2, (x: number) => x + 1);
assertEquals(f(1), 3);
assertEquals(f(2), 5);
assertEquals(f(3), 7);
});

38
composition.ts Normal file
View File

@ -0,0 +1,38 @@
/**
* 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);
}
/**
* Pipe the result of a function as first parameter of another, to create a new function
*/
export function pipe<IN extends any[], INT, R>(
f1: (...args: IN) => INT,
f2: (value: INT) => R,
): (...args: IN) => R {
return (...args: IN) => f2(f1(...args));
}

47
iterables.test.ts Normal file
View File

@ -0,0 +1,47 @@
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { at, first, fmap, last, second, third } from "./iterables.ts";
Deno.test("at", () => {
const second = at(1);
assertEquals(second([1, 4, 8]), 4);
assertEquals(second([1]), undefined);
const second_from_end = at(-2);
assertEquals(second_from_end([1, 4, 6, 8]), 6);
assertEquals(second_from_end([1]), undefined);
});
Deno.test("first", () => {
assertEquals(first([1, 4, 8]), 1);
assertEquals(first([1]), 1);
assertEquals(first([]), undefined);
});
Deno.test("second", () => {
assertEquals(second([1, 4, 8]), 4);
assertEquals(second([1]), undefined);
assertEquals(second([]), undefined);
});
Deno.test("third", () => {
assertEquals(third([1, 4, 8]), 8);
assertEquals(third([1, 4]), undefined);
assertEquals(third([]), undefined);
});
Deno.test("last", () => {
assertEquals(last([1, 4, 8]), 8);
assertEquals(last([1]), 1);
assertEquals(last([]), undefined);
});
Deno.test("fmap", () => {
assertEquals(fmap()([1, 2, 3]), [1, 2, 3]);
assertEquals(fmap((x: number) => x * 2)([1, 2, 3]), [2, 4, 6]);
assertEquals(fmap(undefined, (x: number) => x % 2 == 0)([1, 2, 3]), [2]);
assertEquals(fmap((x: number) => x * 2)([1, 2, 3]), [2, 4, 6]);
assertEquals(
fmap((x: number) => x * 2, (x: number) => x < 5)([1, 2, 3]),
[2, 4],
);
});

72
iterables.ts Normal file
View File

@ -0,0 +1,72 @@
/**
* Index getter
*/
export function at<T>(idx: number): (array: ReadonlyArray<T>) => T | undefined {
if (idx < 0) {
return (array) => array[array.length + idx];
} else {
return (array) => array[idx];
}
}
/**
* Array first item getter
*/
export const first: <T extends ReadonlyArray<any>>(
array: T,
) => T[0] | undefined = at(0);
/**
* Array second item getter
*/
export const second: <T extends ReadonlyArray<any>>(
array: T,
) => T[1] | undefined = at(1);
/**
* Array third item getter
*/
export const third: <T extends ReadonlyArray<any>>(
array: T,
) => T[2] | undefined = at(2);
/**
* Array last item getter
*/
export const last: <T>(array: ReadonlyArray<T>) => T | undefined = at(-1);
/**
* Applies map and filter on an array, using a single function
*/
export function fmap<A, T extends A>(
m?: undefined,
f?: (val: A) => val is T,
): (array: A[]) => T[];
export function fmap<A>(
m?: undefined,
f?: (val: A) => boolean,
): (array: A[]) => A[];
export function fmap<A, B, T extends B>(
m: (val: A) => B,
f?: (val: B) => val is T,
): (array: A[]) => T[];
export function fmap<A, B>(
m: (val: A) => B,
f?: (val: B) => boolean,
): (array: A[]) => B[];
export function fmap<A, B>(
m?: (val: A) => B,
f?: (val: A | B) => boolean,
): (array: A[]) => (A | B)[] {
return (array) => {
if (m && f) {
return array.map(m).filter(f);
} else if (m) {
return array.map(m);
} else if (f) {
return array.filter(f);
} else {
return array.slice();
}
};
}

View File

@ -1,23 +0,0 @@
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"
]
}

8
objects.test.ts Normal file
View File

@ -0,0 +1,8 @@
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { attr } from "./objects.ts";
Deno.test("attr", () => {
const getx = attr("x");
assertEquals(getx({ x: 4, y: 5 }), 4);
assertEquals(getx({ x: undefined, y: 5 }), undefined);
});

8
objects.ts Normal file
View File

@ -0,0 +1,8 @@
/**
* Attribute getter
*/
export function attr<K extends string>(
name: K,
): <O extends Record<K, O[K]>>(obj: O) => O[K] {
return (obj) => obj[name];
}

8713
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +0,0 @@
{
"name": "tk-functional",
"version": "0.2.4",
"description": "Typescript/Javascript helpers for functional-style programming",
"source": "src/index.ts",
"main": "dist/tk-functional.umd.js",
"module": "dist/tk-functional.modern.js",
"types": "dist/index.d.ts",
"files": [
"/src",
"/dist"
],
"scripts": {
"test": "jest",
"normalize": "tk-base",
"build": "microbundle build -f modern,umd",
"prepare": "npm run build",
"prepublishOnly": "npm test",
"dev": "run-p dev:*",
"dev:test": "jest --watchAll",
"dev:build": "microbundle watch -f modern,umd",
"dev:serve": "live-server --host=localhost --port=5000 --no-browser dist"
},
"author": {
"name": "Michaël Lemaire",
"url": "https://thunderk.net"
},
"license": "ISC",
"repository": {
"type": "git",
"url": "https://code.thunderk.net/tslib/tk-functional.git"
},
"bugs": {
"url": "https://gitlab.com/thunderk/tk-functional/issues"
},
"homepage": "https://code.thunderk.net/tslib/tk-functional",
"keywords": [
"typescript"
],
"devDependencies": {
"tk-base": "^0.2.5"
},
"dependencies": {}
}

43
predicates.test.ts Normal file
View File

@ -0,0 +1,43 @@
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { always, and, never, not, or } from "./predicates.ts";
Deno.test("always", () => {
assertEquals(always(), true);
assertEquals(always(true), true);
assertEquals(always(false), true);
});
Deno.test("never", () => {
assertEquals(never(), false);
assertEquals(never(true), false);
assertEquals(never(false), false);
});
Deno.test("not", () => {
const iseven = (x: number) => x % 2 == 0;
const isodd = not(iseven);
assertEquals(iseven(0), true);
assertEquals(isodd(0), false);
assertEquals(iseven(1), false);
assertEquals(isodd(1), true);
assertEquals(iseven(2), true);
assertEquals(isodd(2), false);
});
Deno.test("and", () => {
const f = and((x: number) => x % 2 == 0, (x: number) => x > 5);
assertEquals(f(0), false);
assertEquals(f(2), false);
assertEquals(f(8), true);
assertEquals(f(9), false);
assertEquals(f(10), true);
});
Deno.test("or", () => {
const f = or((x: number) => x % 2 == 0, (x: number) => x > 5);
assertEquals(f(0), true);
assertEquals(f(1), false);
assertEquals(f(2), true);
assertEquals(f(8), true);
assertEquals(f(9), true);
});

50
predicates.ts Normal file
View File

@ -0,0 +1,50 @@
/**
* Predicate that always returns true
*/
export const always = (...args: any[]) => true;
/**
* Predicate that always returns false
*/
export const never = (...args: any[]) => false;
/**
* Negate a predicate
*/
export function not<A extends any[]>(
predicate: (...args: A) => boolean,
): (...args: A) => boolean {
return (...args) => !predicate(...args);
}
/**
* Apply a boolean "and" to merge predicates
*/
export function and<A extends any[]>(
...predicates: ((...args: A) => boolean)[]
): (...args: A) => boolean {
return (...args) => {
for (let p of predicates) {
if (!p(...args)) {
return false;
}
}
return true;
};
}
/**
* Apply a boolean "or" to merge predicates
*/
export function or<A extends any[]>(
...predicates: ((...args: A) => boolean)[]
): (...args: A) => boolean {
return (...args) => {
for (let p of predicates) {
if (p(...args)) {
return true;
}
}
return false;
};
}

View File

@ -1,257 +0,0 @@
import { always, and, at, attr, bool, cmp, first, fmap, identity, is, isinstance, last, never, nn, nnu, nop, not, nu, or, partial, pipe, second, third } 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"]);
});
});
describe(always, () => {
it("returns true", () => {
expect(always()).toBe(true);
expect(always(true)).toBe(true);
expect(always(false)).toBe(true);
});
});
describe(never, () => {
it("returns false", () => {
expect(never()).toBe(false);
expect(never(true)).toBe(false);
expect(never(false)).toBe(false);
});
});
describe(not, () => {
it("negates predicate", () => {
const iseven = (x: number) => x % 2 == 0;
const isodd = not(iseven);
expect(iseven(0)).toBe(true);
expect(isodd(0)).toBe(false);
expect(iseven(1)).toBe(false);
expect(isodd(1)).toBe(true);
expect(iseven(2)).toBe(true);
expect(isodd(2)).toBe(false);
});
});
describe(and, () => {
it("combines predicates", () => {
const f = and((x: number) => x % 2 == 0, (x: number) => x > 5);
expect(f(0)).toBe(false);
expect(f(2)).toBe(false);
expect(f(8)).toBe(true);
expect(f(9)).toBe(false);
expect(f(10)).toBe(true);
});
});
describe(or, () => {
it("combines predicates", () => {
const f = or((x: number) => x % 2 == 0, (x: number) => x > 5);
expect(f(0)).toBe(true);
expect(f(1)).toBe(false);
expect(f(2)).toBe(true);
expect(f(8)).toBe(true);
expect(f(9)).toBe(true);
});
});
describe(is, () => {
it("checks for equality", () => {
const f = is(5);
expect(f(5)).toBe(true);
expect(f(4)).toBe(false);
expect(f("a" as any)).toBe(false);
expect(f(null as any)).toBe(false);
expect(f(undefined as any)).toBe(false);
});
it("applies strict equality", () => {
const obj: { x: number } = { x: 1 };
let f = is(obj);
expect(f(obj)).toBe(true);
expect(f({ x: 1 })).toBe(false);
expect(f({ y: 1 } as any)).toBe(false);
});
});
describe(isinstance, () => {
it("checks for subclasses", () => {
class A { }
class A1 extends A { }
class A2 extends A { }
class B { }
const f: any[] = [5, null, undefined, new A, new A1, new B, new A2];
const result = f.filter(isinstance(A));
expect(result).toEqual([f[3], f[4], f[6]]);
});
});
describe(pipe, () => {
it("pipes two functions", () => {
const f = pipe((x: number) => x * 2, (x: number) => x + 1);
expect(f(1)).toBe(3);
expect(f(2)).toBe(5);
expect(f(3)).toBe(7);
});
});
describe(attr, () => {
it("gets an attribute from an object", () => {
const getx = attr("x");
expect(getx({ x: 4, y: 5 })).toBe(4);
expect(getx({ x: undefined, y: 5 })).toBeUndefined();
});
});
describe(at, () => {
it("gets an index in an array", () => {
const second = at(1);
expect(second([1, 4, 8])).toBe(4);
expect(second([1])).toBeUndefined();
});
it("handles negative indices from the end", () => {
const second_from_end = at(-2);
expect(second_from_end([1, 4, 6, 8])).toBe(6);
expect(second_from_end([1])).toBeUndefined();
});
});
describe(first, () => {
it("gets the first item in an array", () => {
expect(first([1, 4, 8])).toBe(1);
expect(first([1])).toBe(1);
expect(first([])).toBeUndefined();
});
});
describe(second, () => {
it("gets the second item in an array", () => {
expect(second([1, 4, 8])).toBe(4);
expect(second([1])).toBeUndefined();
expect(second([])).toBeUndefined();
});
});
describe(third, () => {
it("gets the third item in an array", () => {
expect(third([1, 4, 8])).toBe(8);
expect(third([1, 4])).toBeUndefined();
expect(third([])).toBeUndefined();
});
});
describe(last, () => {
it("gets the last item in an array", () => {
expect(last([1, 4, 8])).toBe(8);
expect(last([1])).toBe(1);
expect(last([])).toBeUndefined();
});
});
describe(nn, () => {
it("checks for null", () => {
expect(nn(undefined)).toBeUndefined();
expect(nn(0)).toBe(0);
expect(nn("")).toBe("");
expect(nn([])).toEqual([]);
expect(() => nn(null)).toThrowError("null value");
});
});
describe(nu, () => {
it("checks for undefined", () => {
expect(nu(null)).toBe(null);
expect(nu(0)).toBe(0);
expect(nu("")).toBe("");
expect(nu([])).toEqual([]);
expect(() => nu(undefined)).toThrowError("undefined value");
});
});
describe(nnu, () => {
it("checks for null or undefined", () => {
expect(nnu(0)).toBe(0);
expect(nnu("")).toBe("");
expect(nnu([])).toEqual([]);
expect(() => nnu(undefined)).toThrowError("undefined value");
expect(() => nnu(null)).toThrowError("null value");
});
});
describe(bool, () => {
it("checks for boolean equivalent", () => {
expect(bool(null)).toBe(false);
expect(bool(undefined)).toBe(false);
expect(bool(false)).toBe(false);
expect(bool(true)).toBe(true);
expect(bool(-1)).toBe(true);
expect(bool(0)).toBe(false);
expect(bool(1)).toBe(true);
expect(bool("")).toBe(false);
expect(bool(" ")).toBe(true);
expect(bool("abc")).toBe(true);
expect(bool([])).toBe(false);
expect(bool([0])).toBe(true);
expect(bool([1, 2, 3])).toBe(true);
expect(bool(new Set())).toBe(false);
expect(bool(new Set([0]))).toBe(true);
expect(bool(new Set([1, 2, 3]))).toBe(true);
expect(bool({})).toBe(false);
expect(bool({ a: 5 })).toBe(true);
class Obj1 { };
class Obj2 { private x = 0 };
expect(bool(new Obj1())).toBe(false);
expect(bool(new Obj2())).toBe(true);
});
});
describe(fmap, () => {
it("applies map and filter on an array", () => {
expect(fmap()([1, 2, 3])).toEqual([1, 2, 3]);
expect(fmap((x: number) => x * 2)([1, 2, 3])).toEqual([2, 4, 6]);
expect(fmap(undefined, (x: number) => x % 2 == 0)([1, 2, 3])).toEqual([2]);
expect(fmap((x: number) => x * 2)([1, 2, 3])).toEqual([2, 4, 6]);
expect(fmap((x: number) => x * 2, (x: number) => x < 5)([1, 2, 3])).toEqual([2, 4]);
});
});

View File

@ -1,207 +0,0 @@
/**
* 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);
}
type cmpArgs = Readonly<{ key: (item: any) => any, reverse: boolean }>
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;
}
}
}
/**
* Predicate that always returns true
*/
export const always = (...args: any[]) => true
/**
* Predicate that always returns false
*/
export const never = (...args: any[]) => false
/**
* Negate a predicate
*/
export function not<A extends any[]>(predicate: (...args: A) => boolean): (...args: A) => boolean {
return (...args) => !predicate(...args);
}
/**
* Apply a boolean "and" to merge predicates
*/
export function and<A extends any[]>(...predicates: ((...args: A) => boolean)[]): (...args: A) => boolean {
return (...args) => {
for (let p of predicates) {
if (!p(...args)) {
return false;
}
}
return true;
}
}
/**
* Apply a boolean "or" to merge predicates
*/
export function or<A extends any[]>(...predicates: ((...args: A) => boolean)[]): (...args: A) => boolean {
return (...args) => {
for (let p of predicates) {
if (p(...args)) {
return true;
}
}
return false;
}
}
/**
* Get a function to check the strict equality with a reference
*/
export function is<T, S extends T>(ref: T): (input: S) => input is Extract<T, S> {
return (x): x is Extract<T, S> => x === ref;
}
/**
* Get a function to check that inputs are instances of a given type
*/
export function isinstance<T, S extends T>(ref: { new(...args: any[]): T }): (input: S) => input is Extract<T, S> {
return (x): x is Extract<T, S> => x instanceof ref;
}
/**
* Pipe the result of a function as first parameter of another, to create a new function
*/
export function pipe<IN extends any[], INT, R>(f1: (...args: IN) => INT, f2: (value: INT) => R): (...args: IN) => R {
return (...args: IN) => f2(f1(...args));
}
/**
* Attribute getter
*/
export function attr<K extends string>(name: K): <O extends Record<K, O[K]>>(obj: O) => O[K] {
return obj => obj[name];
}
/**
* Index getter
*/
export function at<T>(idx: number): (array: ReadonlyArray<T>) => T | undefined {
if (idx < 0) {
return array => array[array.length + idx];
} else {
return array => array[idx];
}
}
/**
* Array first item getter
*/
export const first: <T extends ReadonlyArray<any>>(array: T) => T[0] | undefined = at(0);
/**
* Array second item getter
*/
export const second: <T extends ReadonlyArray<any>>(array: T) => T[1] | undefined = at(1);
/**
* Array third item getter
*/
export const third: <T extends ReadonlyArray<any>>(array: T) => T[2] | undefined = at(2);
/**
* Array last item getter
*/
export const last: <T>(array: ReadonlyArray<T>) => T | undefined = at(-1);
/**
* Check that a value is not null, throwing an error if it is
*/
export function nn<T>(value: T | null): T {
if (value === null) {
throw new Error("null value");
} else {
return value;
}
}
/**
* Check that a value is not undefined, throwing an error if it is
*/
export function nu<T>(value: T | undefined): T {
if (typeof value === "undefined") {
throw new Error("undefined value");
} else {
return value;
}
}
/**
* Check that a value is not null nor undefined, throwing an error if it is
*/
export const nnu: <T>(value: T | null | undefined) => T = pipe(nn, nu);
/**
* Convert a value to a boolean (are considered falsy: 0, false, "", {}, [], null, undefined)
*/
export function bool<T>(value: T | null | undefined): value is T;
export function bool(value: any): boolean {
if (!value) {
return false;
} else if (value instanceof Set) {
return value.size > 0;
} else if (typeof value == "object") {
return Object.keys(value).length > 0;
} else {
return true;
}
}
/**
* Applies map and filter on an array, using a single function
*/
export function fmap<A, T extends A>(m?: undefined, f?: (val: A) => val is T): (array: A[]) => T[];
export function fmap<A>(m?: undefined, f?: (val: A) => boolean): (array: A[]) => A[];
export function fmap<A, B, T extends B>(m: (val: A) => B, f?: (val: B) => val is T): (array: A[]) => T[];
export function fmap<A, B>(m: (val: A) => B, f?: (val: B) => boolean): (array: A[]) => B[];
export function fmap<A, B>(m?: (val: A) => B, f?: (val: A | B) => boolean): (array: A[]) => (A | B)[] {
return array => {
if (m && f) {
return array.map(m).filter(f);
} else if (m) {
return array.map(m);
} else if (f) {
return array.filter(f);
} else {
return array.slice();
}
};
}

View File

@ -1,21 +1,10 @@
{
"compilerOptions": {
"moduleResolution": "node",
"esModuleInterop": true,
"module": "esnext",
"target": "ESNext",
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"preserveConstEnums": true,
"declaration": true,
"target": "es6",
"lib": [
"dom",
"es6"
]
},
"exclude": [
"node_modules",
"dist",
"src/**/*.test.ts"
]
"preserveConstEnums": true
}
}

40
typing.test.ts Normal file
View File

@ -0,0 +1,40 @@
import {
assertEquals,
assertThrows,
} from "https://deno.land/std/testing/asserts.ts";
import { isinstance, nn, nnu, nu } from "./typing.ts";
Deno.test("isinstance", () => {
class A {}
class A1 extends A {}
class A2 extends A {}
class B {}
const f: any[] = [5, null, undefined, new A(), new A1(), new B(), new A2()];
const result = f.filter(isinstance(A));
assertEquals(result, [f[3], f[4], f[6]]);
});
Deno.test("nn", () => {
assertEquals(nn(undefined), undefined);
assertEquals(nn(0), 0);
assertEquals(nn(""), "");
assertEquals(nn([]), []);
assertThrows(() => nn(null), Error, "null value");
});
Deno.test("nu", () => {
assertEquals(nu(null), null);
assertEquals(nu(0), 0);
assertEquals(nu(""), "");
assertEquals(nu([]), []);
assertThrows(() => nu(undefined), Error, "undefined value");
});
Deno.test("nnu", () => {
assertEquals(nnu(0), 0);
assertEquals(nnu(""), "");
assertEquals(nnu([]), []);
assertThrows(() => nnu(undefined), Error, "undefined value");
assertThrows(() => nnu(null), Error, "null value");
});

37
typing.ts Normal file
View File

@ -0,0 +1,37 @@
import { pipe } from "./composition.ts";
/**
* Get a function to check that inputs are instances of a given type
*/
export function isinstance<T, S extends T>(
ref: { new (...args: any[]): T },
): (input: S) => input is Extract<T, S> {
return (x): x is Extract<T, S> => x instanceof ref;
}
/**
* Check that a value is not null, throwing an error if it is
*/
export function nn<T>(value: T | null): T {
if (value === null) {
throw new Error("null value");
} else {
return value;
}
}
/**
* Check that a value is not undefined, throwing an error if it is
*/
export function nu<T>(value: T | undefined): T {
if (typeof value === "undefined") {
throw new Error("undefined value");
} else {
return value;
}
}
/**
* Check that a value is not null nor undefined, throwing an error if it is
*/
export const nnu: <T>(value: T | null | undefined) => T = pipe(nn, nu);