Skip to content

Design Meeting Notes, 3/8/2022 #53169

Closed
Closed
@DanielRosenwasser

Description

@DanielRosenwasser

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design NotesNotes from our design meetings

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions