Closed
Description
Suggestion
π Search Terms
array refine every
β Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
β Suggestion
A way to make Array.every
refine the type of the argument array. This isn't sound, but I think the unsoundness is similar in spirit TypeScript's usual unsoundness around mutability.
π Motivating Example
It would be nice if this worked:
function max1<T>(stuff: Array<string | number>): number {
if (stuff.every(x => typeof x === 'number')) {
Math.max(...stuff); // Error: 'string | number' is not assignable to 'number'.
}
return -1;
}
I tested it out with my own every
function. But to get my original example to work, I needed to annotate the return type of my predicate:
declare function every<T, R extends T>(array: Array<T>, predicate: (value: T) => value is R): array is Array<R>;
function max2<T>(stuff: Array<string | number>): number {
if (every(stuff, x => typeof x === 'number')) { // Error: (x: string | number) => boolean' is not assignable to '(value: string | number) => value is string | number'
Math.max(...stuff);
}
return -1;
}
// Works with explicit type annotation on predicate return type.
function max3<T>(stuff: Array<string | number>): number {
if (every(stuff, (x): x is number => typeof x === 'number')) {
Math.max(...stuff);
}
return -1;
}
π» Use Cases
The original inspiration for this line of thought came from writing a zip
-style function:
function* zip<T>(...iterables: Array<Iterable<T>>): Iterable<Array<T>> {
if (iterables.length === 0) throw new Error('iterables is empty');
// Explicit annotation on 'iterators' because Iterator's second type argument defaults to 'any', which masks the 'r.value' error below.
const iterators: Array<Iterator<T, unknown, never>> = iterables.map(it => it[Symbol.iterator]());
while (true) {
const results = iterators.map(it => it.next());
if (!every(results, r => r.done !== true)) { // Error here, but I don't know what annotation to write.
return;
}
yield results.map(r => r.value); // Error here, because r.value might be for a 'done' iterator
}
}
Metadata
Metadata
Assignees
Labels
No labels