diff --git a/README.md b/README.md index e3300a9..7687b88 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,62 @@ Issues can be reported on [GitLab](https://gitlab.com/thunderk/tk-functional/iss Functions --------- -**nop** does nothing (useful for some callbacks): +**always** and **never** return true and false respectively ```typescript -new ConstructorWithMandatoryCallback(nop) +always() // => true +never() // => false +``` + +**and** and **or** combines 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 +``` + +**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] ``` **identity** returns its argument untouched: @@ -30,11 +82,26 @@ new ConstructorWithMandatoryCallback(nop) a === identity(a) // => true ``` -**always** and **never** return true and false respectively +**nn**, **nu** and **nnu** checks at run-time for null or undefined: ```typescript -always() // => true -never() // => false +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: @@ -45,10 +112,9 @@ const plus1 = partial({a: 1}, sum); plus1({b: 8}) // => 9 ``` -**cmp** simplifies the use of array sorting: +**pipe** chains two functions as one: ```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] +const f = pipe((x: number) => x * 2, (x: number) => x + 1); +f(3) // => 7 ((3 * 2) + 1) ``` diff --git a/package-lock.json b/package-lock.json index e112962..216ace2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "tk-functional", - "version": "0.1.4", + "version": "0.1.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1185,9 +1185,9 @@ "dev": true }, "@types/node": { - "version": "12.7.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.7.tgz", - "integrity": "sha512-4jUncNe2tj1nmrO/34PsRpZqYVnRV1svbU78cKhuQKkMntKB/AmdLyGgswcZKjFHEHGpiY8pVD8CuVI55nP54w==", + "version": "12.7.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.8.tgz", + "integrity": "sha512-FMdVn84tJJdV+xe+53sYiZS4R5yn1mAIxfj+DVoNiQjTYz1+OYmjwEZr1ev9nU0axXwda0QDbYl06QHanRVH3A==", "dev": true }, "@types/q": { @@ -1920,9 +1920,9 @@ } }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz", + "integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==", "dev": true }, "component-emitter": { @@ -2549,9 +2549,9 @@ } }, "electron-to-chromium": { - "version": "1.3.266", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.266.tgz", - "integrity": "sha512-UTuTZ4v8T0gLPHI7U75PXLQePWI65MTS3mckRrnLCkNljHvsutbYs+hn2Ua/RFul3Jt/L3Ht2rLP+dU/AlBfrQ==", + "version": "1.3.269", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.269.tgz", + "integrity": "sha512-t2ZTfo07HxkxTOUbIwMmqHBSnJsC9heqJUm7LwQu2iSk0wNhG4H5cMREtb8XxeCrQABDZ6IqQKY3yZq+NfAqwg==", "dev": true }, "emoji-regex": { @@ -2886,9 +2886,9 @@ } }, "filesize": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-4.2.0.tgz", - "integrity": "sha512-bdS2UP98MZzLyTZzhuSH5ctAWyDt81n5xMti9BSdmgPXjjENLDz5Bmbk2R7ATVw/HRysZzWA2JIPgcSAOimWpw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-4.2.1.tgz", + "integrity": "sha512-bP82Hi8VRZX/TUBKfE24iiUGsB/sfm2WUrwTQyAzQrhO3V9IhcBBNBXMyzLY5orACxRyYJ3d2HeRVX+eFv4lmA==", "dev": true }, "fill-range": { @@ -3628,9 +3628,9 @@ } }, "handlebars": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.3.1.tgz", - "integrity": "sha512-c0HoNHzDiHpBt4Kqe99N8tdLPKAnGCQ73gYMPWtAYM4PwGnf7xl8PBUHJqh9ijlzt2uQKaSRxbXRt+rZ7M2/kA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.0.tgz", + "integrity": "sha512-xkRtOt3/3DzTKMOt3xahj2M/EqNhY988T+imYSlMgs5fVhLN2fmKVVj0LtEGmb+3UUYV5Qmm1052Mm3dIQxOvw==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -5034,6 +5034,12 @@ "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", "dev": true }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5262,9 +5268,9 @@ } }, "node-releases": { - "version": "1.1.32", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.32.tgz", - "integrity": "sha512-VhVknkitq8dqtWoluagsGPn3dxTvN9fwgR59fV3D7sLBHe0JfDramsMI8n8mY//ccq/Kkrf8ZRHRpsyVZ3qw1A==", + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.33.tgz", + "integrity": "sha512-I0V30bWQEoHb+10W8oedVoUrdjW5wIkYm0w7vvcrPO95pZY738m1k77GF5sO0vKg5eXYg9oGtrMAETbgZGm11A==", "dev": true, "requires": { "semver": "^5.3.0" @@ -5303,6 +5309,23 @@ "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", "dev": true }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -5607,6 +5630,12 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "pidtree": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz", + "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==", + "dev": true + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -6349,9 +6378,9 @@ "dev": true }, "react-is": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", - "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==", + "version": "16.10.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.1.tgz", + "integrity": "sha512-BXUMf9sIOPXXZWqr7+c5SeOKJykyVr2u0UDzEf4LNGc6taGkQe1A9DFD07umCIXz45RLr9oAAwZbAJ0Pkknfaw==", "dev": true }, "read-pkg": { @@ -6621,14 +6650,14 @@ } }, "rollup": { - "version": "1.21.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.21.4.tgz", - "integrity": "sha512-Pl512XVCmVzgcBz5h/3Li4oTaoDcmpuFZ+kdhS/wLreALz//WuDAMfomD3QEYl84NkDu6Z6wV9twlcREb4qQsw==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.22.0.tgz", + "integrity": "sha512-x4l4ZrV/Mr/x/jvFTmwROdEAhbZjx16yDRTVSKWh/i4oJDuW2dVEbECT853mybYCz7BAitU8ElGlhx7dNjw3qQ==", "dev": true, "requires": { - "@types/estree": "0.0.39", - "@types/node": "^12.7.5", - "acorn": "^7.0.0" + "@types/estree": "*", + "@types/node": "*", + "acorn": "^7.1.0" }, "dependencies": { "acorn": { @@ -6640,9 +6669,9 @@ } }, "rollup-plugin-alias": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-alias/-/rollup-plugin-alias-2.0.0.tgz", - "integrity": "sha512-JVwxV9nwzjc0q7JmrV9jvZlJ8FykNd5r7RmYps8i/7v+vcmmVpeMvXrljhCboYErf4VZ2z9FEj+fP7eJlEGfug==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-alias/-/rollup-plugin-alias-2.0.1.tgz", + "integrity": "sha512-/wOySYmFzR1TPijsiNBOAV12HYPs7GZtAuim2LKdk5V6RnQSZ34ueVgkFfjJffe/HIFR6Jdwc4+7YOlY5uty4w==", "dev": true, "requires": { "slash": "^3.0.0" @@ -6964,6 +6993,12 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "dev": true + }, "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", @@ -7306,6 +7341,17 @@ "strip-ansi": "^5.1.0" } }, + "string.prototype.padend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz", + "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.4.3", + "function-bind": "^1.0.2" + } + }, "string.prototype.trimleft": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", @@ -7414,9 +7460,9 @@ "dev": true }, "terser": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.2.tgz", - "integrity": "sha512-obxk4x19Zlzj9zY4QeXj9iPCb5W8YGn4v3pn4/fHj0Nw8+R7N02Kvwvz9VpOItCZZD8RC+vnYCDL0gP6FAJ7Xg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.4.tgz", + "integrity": "sha512-Kcrn3RiW8NtHBP0ssOAzwa2MsIRQ8lJWiBG/K7JgqPlomA3mtb2DEmp4/hrUA+Jujx+WZ02zqd7GYD+QRBB/2Q==", "dev": true, "requires": { "commander": "^2.20.0", @@ -7459,15 +7505,16 @@ } }, "tk-base": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/tk-base/-/tk-base-0.2.0.tgz", - "integrity": "sha512-ZdXXxHxjByinZy1ZOo26WOjqwkDkQBAFr0U0pKHMtw7tyJyTvMrxxaf/gm6IoaJLcMQ+vdwqFP5t0nmVyhTrog==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tk-base/-/tk-base-0.2.1.tgz", + "integrity": "sha512-oVFVHXYoknqLBSZRqJSg/+BrKAh24cMpXQyU/q0hH+dVPnd+uHQ1nsjE8U69phrYOSCWicVXH1CKfuaGtV0vWg==", "dev": true, "requires": { "@types/jest": "^24.0.18", - "@types/node": "^12.7.7", + "@types/node": "^12.7.8", "jest": "^24.9.0", "microbundle": "^0.12.0-next.6", + "npm-run-all": "^4.1.5", "ts-jest": "^24.1.0", "ts-node": "^8.4.1", "typescript": "^3.6.3" diff --git a/package.json b/package.json index 09cfe19..a8e80b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tk-functional", - "version": "0.1.4", + "version": "0.1.5", "description": "Typescript/Javascript helpers for functional-style programming", "source": "src/index.ts", "main": "dist/tk-functional.umd.js", @@ -11,19 +11,33 @@ "/dist" ], "scripts": { - "test": "npx --no-install tk-base test", - "normalize": "npx --no-install tk-base normalize", - "build": "npx --no-install tk-base build", + "test": "jest", + "normalize": "tk-base", + "build": "microbundle build -f modern,umd", "prepare": "npm run build", - "prepublishOnly": "npm test" + "prepublishOnly": "npm test", + "dev": "run-p dev:*", + "dev:test": "jest --watchAll", + "dev:build": "microbundle watch -f modern,umd" }, "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.0" + "tk-base": "^0.2.1" }, "dependencies": {} } \ No newline at end of file diff --git a/src/index.test.ts b/src/index.test.ts index 897a946..222545f 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,19 +1,19 @@ -import { always, cmp, identity, never, nop, partial } from "./index"; +import { always, and, attr, bool, cmp, fmap, identity, never, nn, nnu, nop, nu, or, partial, pipe } from "./index"; -describe("nop", () => { +describe(nop, () => { it("does nothing", () => { expect(nop()).toBeUndefined(); }); }); -describe("identity", () => { +describe(identity, () => { it("returns the argument untouched", () => { expect(identity(5)).toBe(5); expect(identity(null)).toBe(null); }); }); -describe("partial", () => { +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); @@ -27,7 +27,7 @@ describe("partial", () => { }); }); -describe("cmp", () => { +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]); @@ -36,7 +36,7 @@ describe("cmp", () => { }); }); -describe("always", () => { +describe(always, () => { it("returns true", () => { expect(always()).toBe(true); expect(always(true)).toBe(true); @@ -44,10 +44,123 @@ describe("always", () => { }); }); -describe("never", () => { +describe(never, () => { it("returns false", () => { expect(never()).toBe(false); expect(never(true)).toBe(false); expect(never(false)).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(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(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]); + }); +}); diff --git a/src/index.ts b/src/index.ts index 9c7886b..e9d1fe0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,3 +47,110 @@ export const always = (...args: any[]) => true * Predicate that always returns false */ export const never = (...args: any[]) => false + + +/** + * Apply a boolean "and" to merge predicates + */ +export function and(...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(...predicates: ((...args: A) => boolean)[]): (...args: A) => boolean { + return (...args) => { + for (let p of predicates) { + if (p(...args)) { + return true; + } + } + return false; + } +} + +/** + * Pipe the result of a function as first parameter of another, to create a new function + */ +export function pipe(f1: (...args: IN) => INT, f2: (value: INT) => R): (...args: IN) => R { + return (...args: IN) => f2(f1(...args)); +} + +/** + * Attribute getter + */ +export function attr(name: K): >(obj: O) => V { + return obj => obj[name]; +} + +/** + * Check that a value is not null, throwing an error if it is + */ +export function nn(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(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: (value: T | null | undefined) => T = pipe(nn, nu); + +/** + * Convert a value to a boolean (are considered falsy: 0, false, "", {}, [], null, undefined) + */ +export function bool(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(m?: undefined, f?: (val: A) => val is T): (array: A[]) => T[]; +export function fmap(m?: undefined, f?: (val: A) => boolean): (array: A[]) => A[]; +export function fmap(m: (val: A) => B, f?: (val: B) => val is T): (array: A[]) => T[]; +export function fmap(m: (val: A) => B, f?: (val: B) => boolean): (array: A[]) => B[]; +export function fmap(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(); + } + }; +}