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();
+ }
+ };
+}