Skip to content

Commit

Permalink
fix(symboltuple): types
Browse files Browse the repository at this point in the history
  • Loading branch information
slikts committed Oct 20, 2019
1 parent 70bc995 commit 15bedb3
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 36 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,6 @@ import { UnsafeTuple as Tuple } from 'tuplerone';
Tuple(1, 2, 3) === Tuple(1, 2, 3); // → true
```

If any of the members are objects, `UnsafeTuple` will work the same as a regular `Tuple` and reuse the same cache.

### Can't be compared with operators like `<` or `>`

tuplerone tuples are not supported by the relation comparison operators like `<`, whereas in a language like Python the following (comparing tuples by arity) would evaluate to true: `(1,) < (1, 2)`.
Expand Down
76 changes: 60 additions & 16 deletions src/Tuple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import {
Tuple6,
Tuple7,
Tuple8,
TupleSymbol,
TupleSymbol2,
TupleSymbol3,
TupleSymbol4,
TupleSymbol5,
TupleSymbol6,
TupleSymbol7,
TupleSymbol8,
SymbolTuple,
SymbolTuple0,
SymbolTuple1,
SymbolTuple2,
SymbolTuple3,
SymbolTuple4,
SymbolTuple5,
SymbolTuple6,
SymbolTuple7,
SymbolTuple8,
} from './types';
import { assignArraylike, arraylikeToIterable, getDefaultLazy, isObject } from './helpers';

Expand Down Expand Up @@ -77,14 +79,47 @@ export default class Tuple<A> extends (Array as any) implements ArrayLike<A>, It
}
return getDefaultLazy(tupleKey, () => new Tuple(values, localToken), getLeaf(values));
}

static symbol(...values: any[]): symbol {
return getDefaultLazy(symbolKey, () => makeSymbol(values), getLeaf(values));
static symbol<A, B, C, D, E, F, G, H>(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
g: G,
h: H,
): SymbolTuple8<A, B, C, D, E, F, G, H>;
static symbol<A, B, C, D, E, F, G>(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
g: G,
): SymbolTuple7<A, B, C, D, E, F, G>;
static symbol<A, B, C, D, E, F>(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
): SymbolTuple6<A, B, C, D, E, F>;
static symbol<A, B, C, D, E>(a: A, b: B, c: C, d: D, e: E): SymbolTuple5<A, B, C, D, E>;
static symbol<A, B, C, D>(a: A, b: B, c: C, d: D): SymbolTuple4<A, B, C, D>;
static symbol<A, B, C>(a: A, b: B, c: C): SymbolTuple3<A, B, C>;
static symbol<A, B>(a: A, b: B): SymbolTuple2<A, B>;
static symbol<A>(a: A): SymbolTuple1<A>;
static symbol(): typeof SymbolTuple0;
static symbol(...values: any[]): any {
return getDefaultLazy(symbolKey, () => Symbol(), getLeaf(values));
}

// The exported member is cast as the same type as Tuple.tuple() to avoid duplicating the overloads
static unsafe(...values: any[]): any {
return getDefaultLazy(
unsafeKey,
tupleKey,
() => new UnsafeTuple(values, localToken),
getUnsafeLeaf(values),
);
Expand All @@ -95,16 +130,22 @@ export default class Tuple<A> extends (Array as any) implements ArrayLike<A>, It
}
}

// Cache keys for each tuple type
const tupleKey = Symbol();
const symbolKey = Symbol();
const unsafeKey = Symbol();
const typeOf = (x: unknown): string => typeof x;
const makeSymbol = (values: any[]): symbol => Symbol(String(values.map(typeOf)));

const cache = new WeakishMap();

// Token used to prevent calling the constructor from other modules
const localToken = Symbol();

const initWeakish = () => new WeakishMap();
let tuple0: Tuple0;

/**
* Tries to use the first object from value list as the root key and throws
* if there's no objects.
*/
export const getLeaf = (values: any[]): WeakishMap<any, any> => {
const rootValue = values.find(isObject);
if (!rootValue) {
Expand All @@ -116,9 +157,12 @@ export const getLeaf = (values: any[]): WeakishMap<any, any> => {
return values.reduce((p, c) => getDefaultLazy(c, initWeakish, p), root);
};

// Unsafe tuples aren't garbage collected so it's more efficient to just use a normal map
const unsafeCache = new Map();
const initUnsafe = () => new Map();
class UnsafeTuple<A> extends Tuple<A> {}
export const getUnsafeLeaf = (values: any[]): Map<any, any> =>
values.reduce((p, c) => getDefaultLazy(c, initWeakish, p), cache);
values.reduce((p, c) => getDefaultLazy(c, initUnsafe, p), unsafeCache);

export const { tuple, symbol, unsafe } = Tuple;

Expand Down
18 changes: 10 additions & 8 deletions src/tuplerone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ export {
Tuple6,
Tuple7,
Tuple8,
TupleSymbol,
TupleSymbol2,
TupleSymbol3,
TupleSymbol4,
TupleSymbol5,
TupleSymbol6,
TupleSymbol7,
TupleSymbol8,
SymbolTuple as SymbolTupleType,
SymbolTuple0,
SymbolTuple1,
SymbolTuple2,
SymbolTuple3,
SymbolTuple4,
SymbolTuple5,
SymbolTuple6,
SymbolTuple7,
SymbolTuple8,
} from './types';
21 changes: 13 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,19 @@ export interface Tuple8<A, B, C, D, E, F, G, H> extends Tuple<A | B | C | D | E
readonly length: 8;
}

export type TupleSymbol<A> = symbol;
export type TupleSymbol2<A, B> = TupleSymbol<A | B>;
export type TupleSymbol3<A, B, C> = TupleSymbol<A | B | C>;
export type TupleSymbol4<A, B, C, D> = TupleSymbol<A | B | C | D>;
export type TupleSymbol5<A, B, C, D, E> = TupleSymbol<A | B | C | D | E>;
export type TupleSymbol6<A, B, C, D, E, F> = TupleSymbol<A | B | C | D | E | F>;
export type TupleSymbol7<A, B, C, D, E, F, G> = TupleSymbol<A | B | C | D | E | F | G>;
export type TupleSymbol8<A, B, C, D, E, F, G, H> = TupleSymbol<A | B | C | D | E | F | G | H>;
export type SymbolTuple<T> = {
t: T;
} & symbol;
// tslint:disable-next-line: variable-name
export const SymbolTuple0: SymbolTuple<[never]> = Symbol('SymbolTuple0') as any;
export type SymbolTuple1<A> = SymbolTuple<[A]>;
export type SymbolTuple2<A, B> = SymbolTuple<[A, B]>;
export type SymbolTuple3<A, B, C> = SymbolTuple<[A, B, C]>;
export type SymbolTuple4<A, B, C, D> = SymbolTuple<[A, B, C, D]>;
export type SymbolTuple5<A, B, C, D, E> = SymbolTuple<[A, B, C, D, E]>;
export type SymbolTuple6<A, B, C, D, E, F> = SymbolTuple<[A, B, C, D, E, F]>;
export type SymbolTuple7<A, B, C, D, E, F, G> = SymbolTuple<[A, B, C, D, E, F, G]>;
export type SymbolTuple8<A, B, C, D, E, F, G, H> = SymbolTuple<[A, B, C, D, E, F, G, H]>;

export interface Indexable<A> {
[i: number]: A;
Expand Down
2 changes: 1 addition & 1 deletion test/Tuple.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Tuple from '../src/Tuple';

describe('Tuple', () => {
const a = Object('a');
const a = {};
const { tuple } = Tuple;
it('constructor throws', () => {
expect(() => new (Tuple as any)([1, {}], null)).toThrow();
Expand Down
2 changes: 1 addition & 1 deletion test/tuplerone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('tuple', () => {

describe('tuple symbol', () => {
it('is of type symbol', () => {
expect(typeof SymbolTuple(1, 2, {})).toBe('symbol');
expect(typeof SymbolTuple(1 as const, 2, {})).toBe('symbol');
});

it('compares', () => {
Expand Down

0 comments on commit 15bedb3

Please sign in to comment.