You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Preserve tuples when mapping with as clauses #40586
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:
typetuple=[0,1,2]typeMapped<T>={[KinkeyofT]: T[K]|undefined}typeMappedAs<T>={[KinkeyofTasK]: T[K]|undefined}typetest0=Mapped<tuple>// tuple shape is preservedtypetest1=MappedAs<tuple>// tuple shape is object now
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.
Reacted by Joe Calzaretta and Titian Cernicova-Dragomir
My use-case was for handling _ placeholders on ramda's curry. It allows one to pass a parameter's argument later if _ was passed as an argument. If _ is found then we keep its parameter's type for later, otherwise it gets consumed (filtered out). So this is to update the parameters after consumption, not sure if I can do this with a sparse array?
This would be awesome if it could also preserve tuple labels after filtering. I got a version of curry that preserves tuple labels (from the curried function's params) by splitting up the individual parameters and then re-joining them after sorting - it's painful.
I kind of expect tuples/arrays to stay tuples/arrays if the as clause only produces numeric or number-like keys (I mean, K in keyof T as `get${K}` cannot reasonably stay a tuple/array, right?). If a numeric index in the middle is missing (so "0","1","3"), I expect a sparse tuple, not a shortened one. There's no way I can parse K in keyof T as Exclude<K, "2"> to somehow turn "3" into "2" without taking crazy pills first.
Now maybe if we suppress indices at the end it can produce a shortened tuple, since the as clause maps numeric keys in a straightforward way. So I'd be on board with preserving arrays/tuples in all cases where the resultant keys are numericlike, and making the output tuple have a length one larger than the largest mapped numeric key in the output type. Like this:
typeKillTwo<T>={[KinkeyofTasExclude<K,"2">]: T[K]};typeA=KillTwo<["a","b","c","d"]>;// ["a", "b", undefined, "d"] or ["a", "b", never, "d"] or ??typeB=KillTwo<["a","b","c"]>;// ["a", "b"]
Joe Calzaretta (@jcalz) Why not allow to delete entries, to create a real filter? That's how the current feature works with objects. On top of that, if we want the behavior you describe KillTwo could be written with:
typeKillTwo<T>={[KinkeyofT]: Kextends"2" ? never : T[K]};
This is the reason why I'm asking for as to serve as a filter, just like it does for objects. I want filtering capabilities because recursive types are heavy and don't preserve labels. On top of that, we could write Pick & Omit that work both on lists and objects - what is the point of getting an ugly object type if you Pick fields in a tuple?
If all the keys are numeric, then the base (tuple/array) shape is preserved.
If the tuple is somehow enlarged (sparse), then typescript should make an array out of it.
If the tuple is shortened, the concerned keys are removed and the indexes recalculated.
Filtering keys would work on tuples, but do nothing on arrays:
I would be exceptionally surprised if what came out were { zero: "a"; one: "b"; two: "d" } and I'm guessing you would be also. But in my view this is exactly what you're asking for from this:
typeAlsoHmm=MyOmit<["a","b","c","d"],"2">// ???
If the tuple is shortened, the concerned keys are removed and the indexes recalculated.
I would love to be able to manipulate tuples at the type level, but on what grounds would you argue that as clauses should result in recalculating indices, as opposed to some other mechanism that would do this? I understand that it would be useful, but I don't see how to justify it.
but on what grounds would you argue that as clauses should result in recalculating indices, as opposed to some other mechanism that would do this
I justified (to myself) that as was a good candidate for this feature because:
I want to see a standardization of the type system. A single way to deal with arrays/tuples/objects. One mapped type to rule them all.
I'm not opposed to having another mechanism at all, but I aim for simplicity. Now that we have as, I felt that because mapped types already preserve tuple/array shapes, why not mapped types with as clauses?
I don't see a case where we would like to see a tuple/array become an object. Most of the time, we would like to preserve its original shape.
In fact, I'm asking for better mapped types for tuples/array, and a standard way to write mapped types that work for all data types.
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 typetypeFilterNumbers<T>={[KinkeyofTasT[K]extendsnumber ? never : K]: T[K];}// as a mapped array/objectfunctionfilterNumbers(t: {[k: string]: any}){constret: {[k: string]: any}=Array.isArray(t) ? [] : {};for(letkint)(typeoft[k]==="number") ? (deleteret[k]) : (ret[k]=t[k]);returnret;}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!
Reacted by pierre, ExE Boss and Jason Killian
Reacted by pierre, ExE Boss, insidewhy and Jason Killian
I'm reading sparse array a lot here, I guess sparse tuple is what is intended. I assume that would be a tuple with never types in one or more positions. Producing a sparse tuple would be cool if there's a way to compact it. You'd probably see it being compacted the vast majority of the time when filtering keys. The verbosity is okay if it makes things clearer. Without being able to compact a sparse tuple, producing such a tuple would not be useful for most of our use cases (mine is to produce a parser combinator library).
Activity
DanielRosenwasser commentedon 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.
millsp commentedon Sep 16, 2020
My use-case was for handling
_placeholders on ramda'scurry. It allows one to pass a parameter's argument later if_was passed as an argument. If_is found then we keep its parameter's type for later, otherwise it gets consumed (filtered out). So this is to update the parameters after consumption, not sure if I can do this with a sparse array?millsp commentedon Sep 16, 2020
We could also have
PickandOmitfor tuples thanks to this. An implementation ofPickandOmitthat works for both objects and tuples.millsp commentedon Sep 17, 2020
This would be awesome if it could also preserve tuple labels after filtering. I got a version of curry that preserves tuple labels (from the curried function's params) by splitting up the individual parameters and then re-joining them after sorting - it's painful.
jcalz commentedon Sep 24, 2020
I kind of expect tuples/arrays to stay tuples/arrays if the
asclause only produces numeric or number-like keys (I mean,K in keyof T as `get${K}`cannot reasonably stay a tuple/array, right?). If a numeric index in the middle is missing (so"0","1","3"), I expect a sparse tuple, not a shortened one. There's no way I can parseK in keyof T as Exclude<K, "2">to somehow turn"3"into"2"without taking crazy pills first.Now maybe if we suppress indices at the end it can produce a shortened tuple, since the
asclause maps numeric keys in a straightforward way. So I'd be on board with preserving arrays/tuples in all cases where the resultant keys are numericlike, and making the output tuple have alengthone larger than the largest mapped numeric key in the output type. Like this:millsp commentedon Sep 25, 2020
Joe Calzaretta (@jcalz) Why not allow to delete entries, to create a real filter? That's how the current feature works with objects. On top of that, if we want the behavior you describe
KillTwocould be written with:This is the reason why I'm asking for
asto serve as a filter, just like it does for objects. I want filtering capabilities because recursive types are heavy and don't preserve labels. On top of that, we could writePick&Omitthat work both on lists and objects - what is the point of getting an ugly object type if youPickfields in a tuple?If all the keys are numeric, then the base (tuple/array) shape is preserved.
If the tuple is somehow enlarged (sparse), then typescript should make an array out of it.
If the tuple is shortened, the concerned keys are removed and the indexes recalculated.
Filtering keys would work on tuples, but do nothing on arrays:
Arrays don't have specific keys,
Exclude<K, "2">will always result innumberfor arraysBoth array and tuple will match the condition
T[K] extends 2, making filtering possible.jcalz commentedon Sep 25, 2020
Does it, though? I think you and I must have different ideas on what is considered a filter. Here's an object:
I would be exceptionally surprised if what came out were
{ zero: "a"; one: "b"; two: "d" }and I'm guessing you would be also. But in my view this is exactly what you're asking for from this:I would love to be able to manipulate tuples at the type level, but on what grounds would you argue that
asclauses should result in recalculating indices, as opposed to some other mechanism that would do this? I understand that it would be useful, but I don't see how to justify it.millsp commentedon Sep 25, 2020
I justified (to myself) that
aswas a good candidate for this feature because:I want to see a standardization of the type system. A single way to deal with arrays/tuples/objects. One mapped type to rule them all.
I'm not opposed to having another mechanism at all, but I aim for simplicity. Now that we have
as, I felt that because mapped types already preserve tuple/array shapes, why not mapped types withasclauses?I don't see a case where we would like to see a tuple/array become an object. Most of the time, we would like to preserve its original shape.
In fact, I'm asking for better mapped types for tuples/array, and a standard way to write mapped types that work for all data types.
jcalz commentedon 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
asclauses 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:
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
asclauses should result in that.It certainly can't be done by a straightforward reading of
K in keyof T as F<K>whereF<K>is always eitherKornever. ThatKis a real index from the keys ofT. If it gets mapped tonever, then that key is not present in the output type. If it gets mapped toKthen 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!
insidewhy commentedon Dec 28, 2020
I'm reading
sparse arraya lot here, I guesssparse tupleis what is intended. I assume that would be a tuple withnevertypes in one or more positions. Producing a sparse tuple would be cool if there's a way to compact it. You'd probably see it being compacted the vast majority of the time when filtering keys. The verbosity is okay if it makes things clearer. Without being able to compact a sparse tuple, producing such a tuple would not be useful for most of our use cases (mine is to produce a parser combinator library).