question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Preserve tuples when mapping with `as` clauses

See original GitHub issue

Search Terms

#tuple #mapped #as #clause #preserve

Suggestion

New mapped typeā€™s as clause allows one to filter the entries of an object. Basic mapped types preserve the shape of a tuple but not when used with the new as clause:

type tuple = [0, 1, 2]

type Mapped<T> = {
    [K in keyof T]: T[K] | undefined
}

type MappedAs<T> = {
    [K in keyof T as K]: T[K] | undefined
}
type test0 = Mapped<tuple>   // tuple shape is preserved
type test1 = MappedAs<tuple> // tuple shape is object now
test0 === [0 | undefined, 1 | undefined, 2 | undefined]
test1 === {
    [x: number]: 0 | 1 | 2 | undefined;
    0: 0 | undefined;
    1: 1 | undefined;
    2: 2 | undefined;
    length: 3 | undefined;
    toString: (() => string) | undefined;
    toLocaleString: (() => string) | undefined;
    ...
}

Use Cases

  • Filtering lists without having to rely on costly recursive types

Examples

type FilterNumbers<T> = {
    [K in keyof T as T[K] extends number ? never : K]: T[K] | undefined
}

type test2 = FilterNumbers<[1, '2', 3, '4']> // ['2', '4']

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, etc.)
  • This feature would agree with the rest of TypeScriptā€™s Design Goals.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:10 (9 by maintainers)

github_iconTop GitHub Comments

8reactions
jcalzcommented, Sep 26, 2020

I find myself mostly agreeing with all those points, so maybe weā€™re talking past each other? Iā€™m going to try one more time and then stop:

I am in favor of as clauses mapping tuples to tuples and arrays to arrays, as long as numeric-like indices map to numeric-like indices in the clause. If thatā€™s all you were asking for Iā€™d be šŸ‘šŸ‘šŸ‘. I donā€™t want to see a tuple become an object either.

But deleting numeric keys from an array does not logically result in a shorter filtered array; it results in a sparse array of the same length:

// as a mapped type
type FilterNumbers<T> = {
    [K in keyof T as T[K] extends number ? never : K]: T[K];
}

// as a mapped array/object
function filterNumbers(t: { [k: string]: any }) {
    const ret: { [k: string]: any } = Array.isArray(t) ? [] : {};
    for (let k in t) (typeof t[k] === "number") ? (delete ret[k]) : (ret[k] = t[k]);
    return ret;
}

console.log(filterNumbers({ one: "a", two: 2, three: "b", four: 3, five: "c" }));
// {one: "a", three: "b", five: "c"}

console.log(filterNumbers(["a", 2, "b", 3, "c"]));
// ["a",  , "b",  ,"c"]

The automatic reindexing youā€™re talking about which turns a sparse array into a shorter array sounds like a great thing to have in the type system, but I just donā€™t see how mapped types with as clauses should result in that.

It certainly canā€™t be done by a straightforward reading of K in keyof T as F<K> where F<K> is always either K or never. That K is a real index from the keys of T. If it gets mapped to never, then that key is not present in the output type. If it gets mapped to K then that key is present in the output type. Thatā€™s how it works with objects, and thatā€™s what Iā€™d expect it to do with tuples also. I donā€™t have much use for sparse tuples, but thatā€™s what Iā€™d expect to see here.

It really feels like Anders gave us a screwdriver and we are now trying to use it to drive nails into a wall. But for that I want a hammer, not a screwdriver which auto-detects nails and sprouts a hammer head. Honestly, though, if such a screwdriver-with-optional-hammer-abilities were given to us, Iā€™m sure Iā€™d use it too. And maybe even be happy about it. But Iā€™d sure feel awkward when explaining it to people on Stack Overflow.

Anyway, good luck with this feature request!

2reactions
DanielRosenwassercommented, Sep 16, 2020

I believe this was a conscious design decision, and in this case I think compacting a tuple to a shorter length is pretty surprising to me. I would have expected a sparse array type in this case.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Tuple or list when using 'in' in an 'if' clause? - python
The CPython interpreter replaces the second form with the first. That's because loading the tuple from a constant is one operation,Ā ...
Read more >
Using tuple-wrapping to improve `with` expressions in Elixir
The with expression allows us to declare clauses and their expected results. The clauses are executed in order until the end of the...
Read more >
Self-organizing Tuple Reconstruction in Column-stores
First, in order to be able to align all maps in one go we need to actually materialize and maintain all possible maps...
Read more >
Python Tuples - GeeksforGeeks
Deleting a Tuple ... Tuples are immutable and hence they do not allow deletion of a part of it. The entire tuple gets...
Read more >
8. Compound statements ā€” Python 3.11.1 documentation
A compound statement consists of one or more 'clauses. ... or a tuple containing an item that is the class or a non-virtual...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found