After exploring multiple versions of the Omit
implementation, including the one displayed by Intellisense when hovering over Omit
, I'm struggling to grasp why certain implementations are homomorphic while others are not.
Through my investigation, I've discovered that:
- The implementation revealed by hovering over
Omit
is incorrect - The hover-over version does not maintain property 'optionality' (i.e., it's not homomorphic) and differs from the actual implementation that does preserve 'optionality'.
- Two other implementations I've attempted also lack homomorphism, and this puzzles me.
Below is the code I've been working with:
// a type with optional and readonly properties
type HasOptional = { a: number; b?: number, c: string; d?: readonly string[]; };
// initial attempt
type Omit_1<T, K extends keyof T> = { [P in Exclude<keyof T, K>]: T[P]; };
type Omit_1_Optional = Omit_1<HasOptional, 'a'>; // b, d lose optionality
// 'fake' implementation of Omit as shown by Intellisense
type Omit_2<T, K extends string | number | symbol> = { [P in Exclude<keyof T, K>]: T[P]; };
type Omit_2_Optional = Omit_2<HasOptional, 'a'>; // b, d lose optionality
// Utilizing Omit directly
type Omit_3<T, K extends string | number | symbol> = Omit<T, K>;
type Omit_3_Optional = Omit_3<HasOptional, 'a'>; // maintains optionality!
// Explicitly writing Omit's implementation
type Omit_4<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Omit_4_Optional = Omit_4<HasOptional, 'a'>; // maintains optionality!
In a discussion about deep Omit on Stack Overflow, it was mentioned that using [P in K]:
introduces an extra layer of indirection for homomorphic behavior. However, even with that in place, the first two implementations fail to uphold 'optionality'.