Using a Homomorphic Mapped Type For Omit
#53134
interface Obj {
readonly a: string;
b?: number;
c: boolean;
}
// "homomorphic" - preserves all modifiers in BoxedObj
type Boxify<T> = {
[K in keyof T]: { value: T[K] }
}
type BoxedObj = Boxify<Obj>;
// non-"homomorphic" - loses information for BoxedObj2
type Boxify2<Keys extends keyof Obj> = {
[K in Keys]: { value: Obj[K] }
}
type BoxedObj2 = Boxify2<keyof Obj>;
-
Today, Omit is non-homomorphic. Will not preserve shape.
- Why?
keyof on its own does not retain its source type.
-
Today, we can write Omit with an as clause with Exclude - still considered "homomorphic" (even though at this point "homomorphic" is a misnomer).
type Omit<T, DroppedKeys extends PropertyKey> = {
[K in keyof T as Exclude<K, DroppedKeys>]: T[K]
};
-
Switching Omit at this point would likely be breaky.
- Existing code where
interfaces extends from Omit
-
Using a "homomorphic" Omit makes code flow work better because it can produce union types.
-
Could choose to have Omit (original) and MappedOmit - or Omit (improved) and LegacyOmit.
- Swapping
Omit to the "better" version is breaky - at least one package breaks.
-
We really don't want to have 2 Omits, and want to push on an improved Omit.
- Existing code can write
Pick<T, Omit<keyof T, DroppedKeys>>
-
Wait, should Pick be fixed to use a homomorphic mapped type too?
- OH NOOOOOOOOOOOOOOOOOOOOOOOOOOOO
- Almost had a slam dunk on just fixing
Omit.
-
If you fix Pick, then Omit defined as Pick<T, Exclude<keyof T, DroppedKeys>> is also homomorphic, right?
- Almost - but
Omit is not going to distribute, when we write Exclude<keyof T, DroppedKeys>
-
So each of them need to be written as their own mapped types written in terms of keyof T.
-
Note: the as clause for Pick should use an intersection, not Extract
- Why?
- Intersections are much faster for unions of literals.
- What's the difference in the general case?
- For literal types, they're the same.
- For object types and generic types, intersections combine and don't always "eradicate" other types to
never.
Using a Homomorphic Mapped Type For Omit
#53134
Today,
Omitis non-homomorphic. Will not preserve shape.keyofon its own does not retain its source type.Today, we can write
Omitwith anasclause withExclude- still considered "homomorphic" (even though at this point "homomorphic" is a misnomer).Switching
Omitat this point would likely be breaky.interfaces extends fromOmitUsing a "homomorphic"
Omitmakes code flow work better because it can produce union types.Could choose to have
Omit(original) andMappedOmit- orOmit(improved) andLegacyOmit.Omitto the "better" version is breaky - at least one package breaks.We really don't want to have 2
Omits, and want to push on an improvedOmit.Pick<T, Omit<keyof T, DroppedKeys>>Wait, should
Pickbe fixed to use a homomorphic mapped type too?Omit.If you fix
Pick, thenOmitdefined asPick<T, Exclude<keyof T, DroppedKeys>>is also homomorphic, right?Omitis not going to distribute, when we writeExclude<keyof T, DroppedKeys>So each of them need to be written as their own mapped types written in terms of
keyof T.Note: the
asclause forPickshould use an intersection, notExtractnever.