Last active
September 25, 2017 23:15
-
-
Save tdreyno/5f2ca2ade5dd9c2db326b8422a497983 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Vector of number types | |
const v1 = vector([1, 2, 3, 4]); | |
console.log(v1.toString()); | |
// Vector of string types | |
const v2 = vector(["a", "b", "c", "d"]); | |
console.log(v2.toString()); | |
// Vector of vector types | |
const v3 = vector([v1, v1]); | |
console.log(v3.toString()); | |
// Vector combining 2 different types | |
const v4 = v1.concat(v2); | |
console.log(v4.toString()); | |
// Set of number types | |
const s1 = set([1, 2, 1, 2]); | |
console.log(s1.toString()); | |
// Set of string types | |
const s2 = set(["1", "2", "1", "2"]); | |
console.log(s2.toString()); | |
// Set combining 2 different types | |
const s3 = s1.concat(s2); | |
console.log(s3.toString()); | |
// Map of string to number | |
const m1 = map({ | |
a: 1, | |
b: 2 | |
}); | |
console.log(m1.toString()); | |
// Map based on an ES6 Map | |
const m2 = map(new Map([["c", 3], ["d", 4]])); | |
console.log(m2.toString()); | |
// Map with complex, non-string keys | |
const m3 = map(new Map([[v1, v2]])); | |
console.log(m3.toString()); | |
console.log(m3.get(v1)!.toString()); | |
// Record def | |
const person = record<{ | |
name: string; | |
age: number; | |
job: RecordInstance<Job>; | |
}>(); | |
// Subrecord def | |
type Job = { | |
title: string; | |
}; | |
const job = record<Job>(); | |
// Constructor, with type checked params. | |
const thomas = person({ name: "Thomas", age: 34, job: job({ title: "TD" }) }); | |
console.log(thomas.toString()); | |
console.log(thomas.job!.title); | |
// Access via dot, square bracket or getter | |
console.log(thomas.name); | |
console.log(thomas["name"]); | |
console.log(thomas.get("name")); | |
// Setting | |
const t2 = thomas.set("name", "test"); | |
console.log(t2.name); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
vec<1,2,3,4> | |
vec<"a","b","c","d"> | |
vec<vec<1,2,3,4>,vec<1,2,3,4>> | |
vec<1,2,3,4,"a","b","c","d"> | |
set<1,2> | |
set<"1","2"> | |
set<1,2,"1","2"> | |
map<"a": 1, "b": 2> | |
map<"c": 3, "d": 4> | |
map<vec<1,2,3,4>: vec<"a","b","c","d">> | |
vec<"a","b","c","d"> | |
map<"name": "Thomas", "age": 34, "job": {"items":{}}> | |
TD | |
Thomas | |
Thomas | |
Thomas | |
test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function unique<T>(items: Array<T>): Array<T> { | |
return items.reduce( | |
(sum, item) => { | |
if (sum.indexOf(item) !== -1) { | |
return sum; | |
} | |
sum.push(item); | |
return sum; | |
}, | |
[] as Array<T> | |
); | |
} | |
function stringify<T>(obj: T): string { | |
if (obj instanceof ImmutableSet || obj instanceof ImmutableVector) { | |
return obj.toString(); | |
} | |
return JSON.stringify(obj); | |
} | |
type IArrayLike<T> = ImmutableVector<T> | ImmutableSet<T> | Array<T>; | |
abstract class Collection<K, V> { | |
abstract size(): number; | |
abstract toString(): string; | |
abstract map<O>(mapFn: (item: V, key: K) => O): Collection<K, O>; | |
abstract forEach(forEachFn: (item: V, key: K) => any): void; | |
abstract filter(filterFn: (item: V, key: K) => boolean): Collection<K, V>; | |
abstract find(findFn: (item: V, key: K) => boolean): V | undefined; | |
abstract reduce<O>( | |
reduceFn: (sum: O, item: V, key: K) => O, | |
initialValue: O | |
): O; | |
} | |
class ImmutableMap<K, V> extends Collection<K, V> { | |
private readonly items: Map<K, V>; | |
constructor(items: Map<K, V> = new Map<K, V>()) { | |
super(); | |
this.items = items; | |
} | |
size(): number { | |
return this.items.size; | |
} | |
toString(): string { | |
const output = this.reduce( | |
(sum, v, k) => { | |
sum.push(`${stringify(k)}: ${stringify(v)}`); | |
return sum; | |
}, | |
[] as Array<string> | |
); | |
return `map<${output.join(", ")}>`; | |
} | |
has(key: K): boolean { | |
return this.items.has(key); | |
} | |
get(key: K): V | undefined { | |
return this.items.get(key); | |
} | |
set(key: K, value: V): ImmutableMap<K, V> { | |
const dup = new Map<K, V>(this.items); | |
dup.set(key, value); | |
return new ImmutableMap<K, V>(dup); | |
} | |
keys(): Array<K> { | |
const result: Array<K> = []; | |
this.items.forEach((_, key) => result.push(key)); | |
return result; | |
} | |
values(): Array<V> { | |
const result: Array<V> = []; | |
this.items.forEach(value => result.push(value)); | |
return result; | |
} | |
map<O>(mapFn: (item: V, key: K) => O): ImmutableMap<K, O> { | |
const map = this.keys().reduce((sum, key) => { | |
return sum.set(key, mapFn(this.get(key)!, key)); | |
}, new Map<K, O>()); | |
return new ImmutableMap<K, O>(map); | |
} | |
forEach(forEachFn: (value: V, key: K) => any): void { | |
this.items.forEach((value, key) => forEachFn(value, key)); | |
} | |
filter(filterFn: (item: V, key: K) => boolean): ImmutableMap<K, V> { | |
const map = this.keys().reduce((sum, key) => { | |
const value = this.get(key)!; | |
if (!filterFn(value, key)) { | |
return sum; | |
} | |
return sum.set(key, value); | |
}, new Map<K, V>()); | |
// Nothing changed. | |
if (map.size === this.size()) { | |
return this; | |
} | |
return new ImmutableMap<K, V>(map); | |
} | |
find(findFn: (value: V, key: K) => boolean): V | undefined { | |
for (const [k, v] of this.items) { | |
if (findFn(v, k)) { | |
return v; | |
} | |
} | |
} | |
reduce<O>(reduceFn: (sum: O, item: V, key: K) => O, initialValue: O): O { | |
return this.keys().reduce((sum, key) => { | |
return reduceFn(sum, this.get(key)!, key); | |
}, initialValue); | |
} | |
} | |
class ImmutableVector<V> extends Collection<number, V> { | |
private readonly items: Array<V>; | |
constructor(items: Array<V> = []) { | |
super(); | |
this.items = items; | |
} | |
toArray(): Array<V> { | |
return this.items; | |
} | |
join(delimiter: string): string { | |
return this.items.map(stringify).join(delimiter); | |
} | |
toString(): string { | |
return `vec<${this.join(",")}>`; | |
} | |
size(): number { | |
return this.items.length; | |
} | |
get(index: number): V | undefined { | |
return this.items[index]; | |
} | |
includes(value: V): boolean { | |
return !!this.items.find(i => i === value); | |
} | |
first(): V | undefined { | |
return this.get(0); | |
} | |
last(): V | undefined { | |
return this.get(this.items.length - 1); | |
} | |
push<V2>(value: V2): ImmutableVector<V | V2> { | |
return new ImmutableVector<V | V2>([...this.items, value]); | |
} | |
pop(): V | undefined { | |
const dup: Array<V> = [...this.items]; | |
const value = dup[dup.length - 1]; | |
delete dup[dup.length - 1]; | |
return value; | |
} | |
unshift<V2>(value: V2): ImmutableVector<V | V2> { | |
return new ImmutableVector<V | V2>([value, ...this.items]); | |
} | |
shift(): V | undefined { | |
const dup: Array<V> = [...this.items]; | |
const value = dup[0]; | |
delete dup[0]; | |
return value; | |
} | |
set<V2>(index: number, value: V2): ImmutableVector<V | V2> { | |
if ((this.get(index) as any) === value) { | |
return this; | |
} | |
const dup: Array<V | V2> = [...this.items]; | |
dup[index] = value; | |
return new ImmutableVector<V | V2>(dup); | |
} | |
update<V2>( | |
index: number, | |
updaterFn: (currentValue: V | undefined) => V2 | |
): ImmutableVector<V | V2> { | |
const newValue = updaterFn(this.get(index)); | |
return this.set(index, newValue); | |
} | |
delete(index: number): ImmutableVector<V> { | |
if (!this.get(index)) { | |
return this; | |
} | |
const dup: Array<V> = [...this.items]; | |
delete dup[index]; | |
return new ImmutableVector<V>(dup); | |
} | |
slice(startIndex: number, endIndex: number): ImmutableVector<V> { | |
return new ImmutableVector<V>(this.items.slice(startIndex, endIndex)); | |
} | |
indexOf(value: V): number { | |
return this.items.indexOf(value); | |
} | |
findIndex(findFn: (item: V) => boolean): number { | |
return this.items.findIndex(findFn); | |
} | |
concat<V2>(arrayB: IArrayLike<V2>): ImmutableVector<V | V2> { | |
const moreItems = | |
arrayB instanceof ImmutableVector || arrayB instanceof ImmutableSet | |
? arrayB.toArray() | |
: arrayB; | |
return new ImmutableVector<V | V2>([...this.items, ...moreItems]); | |
} | |
map<O>(mapFn: (item: V, key: number) => O): ImmutableVector<O> { | |
return new ImmutableVector(this.items.map(mapFn)); | |
} | |
forEach(forEachFn: (item: V, key: number) => any): void { | |
this.items.forEach(forEachFn); | |
} | |
filter(filterFn: (item: V, key: number) => boolean): ImmutableVector<V> { | |
return new ImmutableVector(this.items.filter(filterFn)); | |
} | |
find(findFn: (item: V, key: number) => boolean): V | undefined { | |
return this.items.find(findFn); | |
} | |
reduce<O>( | |
reduceFn: (sum: O, item: V, key: number, list: Array<V>) => O, | |
initialValue: O | |
): O { | |
return this.items.reduce(reduceFn, initialValue); | |
} | |
unique(): ImmutableVector<V> { | |
return new ImmutableVector<V>(unique(this.items)); | |
} | |
} | |
class ImmutableSet<V> extends Collection<number, V> { | |
private readonly vector: ImmutableVector<V>; | |
constructor(items: Array<V> | ImmutableVector<V> = []) { | |
super(); | |
const vector = | |
items instanceof ImmutableVector ? items : new ImmutableVector(items); | |
this.vector = vector.unique(); | |
} | |
toArray(): Array<V> { | |
return this.vector.toArray(); | |
} | |
join(delimiter: string): string { | |
return this.vector.join(delimiter); | |
} | |
toString() { | |
const stringified = this.map(t => t.toString()).join(","); | |
return `set<${this.join(",")}>`; | |
} | |
size(): number { | |
return this.vector.size(); | |
} | |
includes(value: V): boolean { | |
return this.vector.includes(value); | |
} | |
delete(value: V): ImmutableSet<V> { | |
const existingIndex = this.vector.indexOf(value); | |
if (existingIndex === -1) { | |
return this; | |
} | |
return new ImmutableSet(this.vector.delete(existingIndex)); | |
} | |
concat<V2>(items: IArrayLike<V2>): ImmutableSet<V | V2> { | |
const arrayB = | |
items instanceof ImmutableVector || items instanceof ImmutableSet | |
? items.toArray() | |
: items; | |
return new ImmutableSet<V | V2>(this.vector.concat(arrayB)); | |
} | |
forEach<O>(forEachFn: (item: V, key: number) => O): void { | |
this.vector.forEach(forEachFn); | |
} | |
map<O>(mapFn: (item: V, key: number) => O): ImmutableSet<O> { | |
return new ImmutableSet(this.vector.map(mapFn)); | |
} | |
filter(filterFn: (item: V, key: number) => boolean): ImmutableSet<V> { | |
return new ImmutableSet(this.vector.filter(filterFn)); | |
} | |
find(findFn: (item: V, key: number) => boolean): V | undefined { | |
return this.vector.find(findFn); | |
} | |
reduce<O>( | |
reduceFn: (sum: O, item: V, key: number, list: Array<V>) => O, | |
initialValue: O | |
): O { | |
return this.vector.reduce(reduceFn, initialValue); | |
} | |
} | |
function map<V>(items: { [key: string]: V }): ImmutableMap<string, V>; | |
function map<K, V>(items: Map<K, V>): ImmutableMap<K, V>; | |
function map<K, V>(items: ImmutableMap<K, V>): ImmutableMap<K, V>; | |
function map<K, V>( | |
items: { [key: string]: V } | Map<K, V> | ImmutableMap<K, V> | |
) { | |
if (items instanceof ImmutableMap) { | |
return items; | |
} | |
if (items instanceof Map) { | |
return new ImmutableMap<K, V>(items); | |
} | |
const newMap = new Map<string, V>(); | |
for (const key in items) { | |
if (items.hasOwnProperty(key)) { | |
newMap.set(key, items[key]); | |
} | |
} | |
return new ImmutableMap<string, V>(newMap); | |
} | |
function vector<V>(items: Array<V> | ImmutableVector<V>) { | |
if (items instanceof ImmutableVector) { | |
return items; | |
} | |
return new ImmutableVector<V>(items); | |
} | |
function set<V>(items: Array<V> | ImmutableVector<V>) { | |
return new ImmutableSet<V>( | |
items instanceof ImmutableVector ? items.toArray() : items | |
); | |
} | |
class ImmutableRecord<T extends { readonly [P in keyof T]?: T[P] }> { | |
private readonly map: ImmutableMap<string, any>; | |
constructor(data: Partial<T>) { | |
this.map = map(data); | |
} | |
} | |
type RecordInstance<T> = { readonly [P in keyof T]?: T[P] } & { | |
set<K extends keyof T>(key: K, value: T[K]): RecordInstance<T>; | |
get<K extends keyof T>(key: K): T[K] | undefined; | |
}; | |
function record<T extends object>() { | |
const makeInstance = ( | |
data: Partial<T> | ImmutableMap<string, any> | |
): RecordInstance<T> => { | |
const baseMap: any = data instanceof ImmutableMap ? data : map(data as any); | |
return new Proxy(baseMap, { | |
get: function(baseMap, name) { | |
if (typeof baseMap[name] !== "undefined") { | |
if (name === "set") { | |
return <K extends keyof T>(key: K, value: T[K]) => { | |
const result = baseMap.set(key, value); | |
return result === baseMap ? baseMap : makeInstance(result); | |
}; | |
} | |
return baseMap[name]; | |
} | |
return baseMap.get(name); | |
} | |
}) as any; | |
}; | |
return makeInstance; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment