diff --git a/README.md b/README.md
index 5e59ac7..c05451e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,181 @@
# typescript/functional
[![Build Status](https://thunderk.visualstudio.com/typescript/_apis/build/status/functional?branchName=master)](https://dev.azure.com/thunderk/typescript/_build?pipelineNameFilter=functional)
+
+## About
+
+Provides some common helpers for functional-style programming.
+
+## Import
+
+In deno:
+
+```typescript
+import { bool } from "https://js.thunderk.net/functional/mod.ts";
+```
+
+In browser:
+
+```html
+
+```
+
+## Use
+
+**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]
+```
+
+**defined** removes undefined values from an object:
+
+```typescript
+defined({ a: 1, b: undefined }); // => {a: 1}
+```
+
+**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);
+```
+
+**options** merges options over default values:
+
+```typescript
+options({ a: 1, b: 2, c: 3, d: 4 }, { a: 11, c: 33 }, { b: 22 });
+// => {a: 11, b: 22, c: 33, d: 4}
+```
+
+**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)
+```
diff --git a/comparison.test.ts b/comparison.test.ts
index 29505b0..b369cc7 100644
--- a/comparison.test.ts
+++ b/comparison.test.ts
@@ -1,5 +1,5 @@
import { bool, cmp, is } from "./comparison.ts";
-import { expect, it } from "./deps.test.ts";
+import { expect, it } from "./deps.testing.ts";
it("cmp", () => {
expect([8, 3, 5].sort(cmp())).toEqual([3, 5, 8]);
diff --git a/composition.test.ts b/composition.test.ts
index c3343bb..64cb57d 100644
--- a/composition.test.ts
+++ b/composition.test.ts
@@ -1,5 +1,5 @@
import { identity, nop, partial, pipe } from "./composition.ts";
-import { expect, it } from "./deps.test.ts";
+import { expect, it } from "./deps.testing.ts";
it("nop", () => {
expect(nop()).toBeUndefined();
diff --git a/deps.test.ts b/deps.test.ts
deleted file mode 100644
index c973a09..0000000
--- a/deps.test.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "https://code.thunderk.net/typescript/devtools/raw/1.2.2/testing.ts";
diff --git a/deps.testing.ts b/deps.testing.ts
new file mode 100644
index 0000000..c0653a1
--- /dev/null
+++ b/deps.testing.ts
@@ -0,0 +1,5 @@
+export {
+ describe,
+ expect,
+ it,
+} from "https://js.thunderk.net/devtools@1.3.1/testing.ts";
diff --git a/doc/about.md b/doc/about.md
new file mode 100644
index 0000000..7e45c3b
--- /dev/null
+++ b/doc/about.md
@@ -0,0 +1,3 @@
+## About
+
+Provides some common helpers for functional-style programming.
diff --git a/doc/import.md b/doc/import.md
new file mode 100644
index 0000000..6374c90
--- /dev/null
+++ b/doc/import.md
@@ -0,0 +1,15 @@
+## Import
+
+In deno:
+
+```typescript
+import { bool } from "https://js.thunderk.net/functional/mod.ts";
+```
+
+In browser:
+
+```html
+
+```
diff --git a/doc/index b/doc/index
new file mode 100644
index 0000000..9a75f00
--- /dev/null
+++ b/doc/index
@@ -0,0 +1,3 @@
+about
+import
+use
\ No newline at end of file
diff --git a/doc/use.md b/doc/use.md
new file mode 100644
index 0000000..16f3e46
--- /dev/null
+++ b/doc/use.md
@@ -0,0 +1,157 @@
+## Use
+
+**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]
+```
+
+**defined** removes undefined values from an object:
+
+```typescript
+defined({ a: 1, b: undefined }); // => {a: 1}
+```
+
+**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);
+```
+
+**options** merges options over default values:
+
+```typescript
+options({ a: 1, b: 2, c: 3, d: 4 }, { a: 11, c: 33 }, { b: 22 });
+// => {a: 11, b: 22, c: 33, d: 4}
+```
+
+**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)
+```
diff --git a/iterables.test.ts b/iterables.test.ts
index ad8b3bd..015e935 100644
--- a/iterables.test.ts
+++ b/iterables.test.ts
@@ -1,4 +1,4 @@
-import { expect, it } from "./deps.test.ts";
+import { expect, it } from "./deps.testing.ts";
import { at, first, fmap, last, second, third } from "./iterables.ts";
it("at", () => {
diff --git a/all.ts b/mod.ts
similarity index 100%
rename from all.ts
rename to mod.ts
diff --git a/objects.test.ts b/objects.test.ts
index ad411c8..e8be8ca 100644
--- a/objects.test.ts
+++ b/objects.test.ts
@@ -1,8 +1,31 @@
-import { expect, it } from "./deps.test.ts";
-import { attr } from "./objects.ts";
+import { expect, it } from "./deps.testing.ts";
+import { attr, defined, options } from "./objects.ts";
it("attr", () => {
const getx = attr("x");
expect(getx({ x: 4, y: 5 })).toBe(4);
expect(getx({ x: undefined, y: 5 })).toBeUndefined();
});
+
+it("defined", () => {
+ expect(defined({})).toEqual({});
+ expect(defined({ x: 1 })).toEqual({ x: 1 });
+ expect(defined({ x: 1, y: undefined })).toEqual({ x: 1 });
+
+ const obj = defined({ x: 1, y: undefined });
+ // @ts-expect-error
+ expect(obj.y).toBeUndefined();
+});
+
+it("options", () => {
+ expect(options({})).toEqual({});
+ expect(options({ x: 1, y: 2 }, { x: 3 })).toEqual({ x: 3, y: 2 });
+ expect(options({ x: 1, y: 2 }, { x: 3 }, { x: 4 })).toEqual({ x: 4, y: 2 });
+ expect(options({ x: 1, y: 2 }, { x: 3 }, { y: 0 })).toEqual({ x: 3, y: 0 });
+ expect(options({ x: 1, y: 2 }, { x: undefined, y: 3 })).toEqual({
+ x: 1,
+ y: 3,
+ });
+ // @ts-expect-error
+ expect(options({ x: 1 }, { y: 2 })).toEqual({ x: 1, y: 2 });
+});
diff --git a/objects.ts b/objects.ts
index ea06fa2..23cd265 100644
--- a/objects.ts
+++ b/objects.ts
@@ -1,8 +1,28 @@
-/**
- * Attribute getter
- */
+// Attribute getter
export function attr(
name: K,
): >(obj: O) => O[K] {
return (obj) => obj[name];
}
+
+type DefinedKeys =
+ ({ [P in keyof T]: T[P] extends undefined ? never : P })[keyof T];
+type Defined = Pick>;
+
+// Removes undefined values from objects
+export function defined(obj: T): Defined {
+ return Object.fromEntries(
+ Object.entries(obj).filter(([_, value]) => typeof value !== "undefined"),
+ ) as any;
+}
+
+// Apply options to a default object
+export function options(
+ defaults: T,
+ ...options: Partial[]
+) {
+ for (let opts of options) {
+ defaults = { ...defaults, ...defined(opts) };
+ }
+ return defaults;
+}
diff --git a/predicates.test.ts b/predicates.test.ts
index 2482499..fd1268a 100644
--- a/predicates.test.ts
+++ b/predicates.test.ts
@@ -1,4 +1,4 @@
-import { expect, it } from "./deps.test.ts";
+import { expect, it } from "./deps.testing.ts";
import { always, and, never, not, or } from "./predicates.ts";
it("always", () => {
diff --git a/run b/run
new file mode 100755
index 0000000..74d1c6d
--- /dev/null
+++ b/run
@@ -0,0 +1,19 @@
+#!/bin/sh
+# Simplified run tool for deno commands
+
+if test $# -eq 0
+then
+ echo "Usage: $0 [file or command]"
+ exit 1
+elif echo $1 | grep -q '.*.ts'
+then
+ denocmd=run
+ denoargs=$1
+ shift
+else
+ denocmd=$1
+ shift
+fi
+
+denoargs="$(cat config/$denocmd.flags 2> /dev/null) $denoargs $@"
+exec deno $denocmd $denoargs
diff --git a/typing.test.ts b/typing.test.ts
index 94e1738..7568414 100644
--- a/typing.test.ts
+++ b/typing.test.ts
@@ -1,4 +1,4 @@
-import { expect, it } from "./deps.test.ts";
+import { expect, it } from "./deps.testing.ts";
import { isinstance, nn, nnu, nu } from "./typing.ts";
it("isinstance", () => {