warning:this site is a work in progress, so some things are broken

Homomorphic and non-homomorphic mapped types

3 min read
leia em português
type 
type User = {
    readonly name: string;
    age?: number;
}
User
= {
readonly name: stringname: string age?: number | undefinedage?: number } type
type Mapped = {
    readonly name: string;
    age?: number | undefined;
}
Mapped
= { [function (type parameter) KeyKey in keyof
type User = {
    readonly name: string;
    age?: number;
}
User
]:
type User = {
    readonly name: string;
    age?: number;
}
User
[function (type parameter) KeyKey] }

So nobody ever told you abouthomomorphic mapped typesandnon-homomorphic mapped types? These concepts live inside mapped types and they matter a lot. Let's explore the reason behind a behavior that looks weird at first glance: sometimes the modifiers('readonly'and'?')are kept in a mapped type, and sometimes they aren't.

Note: before we go on, you should already know what mapped types are, deal?

What is a homomorphic mapped type?

A homomorphic mapped type is a mapped type where the compiler can tell you're mapping over the properties of an existing object, so it can keep those properties' modifiers.

Now in practice, here's an example of a mapped type that is homomorphic:

type 
type User = {
    readonly name: string;
    age?: number;
}
User
= {
readonly name: stringname: string age?: number | undefinedage?: number } type
type Homomorphic = {
    readonly name: string;
    age?: number | undefined;
}
Homomorphic
= { [function (type parameter) KeyKey in keyof
type User = {
    readonly name: string;
    age?: number;
}
User
]:
type User = {
    readonly name: string;
    age?: number;
}
User
[function (type parameter) KeyKey] }
// the compiler can recognize the keys // of User and keep their modifiers (readonly and ?) // { // readonly name: string; // age?: number | undefined; // }

What is a non-homomorphic mapped type?

A non-homomorphic mapped type is the opposite: the mapped type doesn't keep the modifiers. Let's see an example:

type 
type User = {
    readonly name: string;
    age?: number;
}
User
= {
readonly name: stringname: string age?: number | undefinedage?: number } type type UserKeys = keyof UserUserKeys = keyof
type User = {
    readonly name: string;
    age?: number;
}
User
// "name" | "age"
type
type NonHomomorphic = {
    name: string;
    age: number | undefined;
}
NonHomomorphic
= { [function (type parameter) KeyKey in type UserKeys = keyof UserUserKeys]:
type User = {
    readonly name: string;
    age?: number;
}
User
[function (type parameter) KeyKey] }
// here the compiler can't make that // connection: all it sees is the // union "name" | "age", so the // modifiers are not kept // { // name: string; // age: number | undefined; // }

After seeing the code above, you might be wondering:"you said the modifiers aren't kept, and they really weren't, so why was the age property inferred as number | undefined if the "?" modifier wasn't kept?"

Playground with the examples