Saltar al contenido
webtype.orgwebtype.org

    ↑↓ mover · ⏎ abrir · esc cerrar

    Concepto 8 de 12

    Distributividad

    La distribución es la regla que sorprende a todo el mundo una vez y luego explica la mitad del comportamiento confuso del sistema de tipos. Un condicional cuyo lado comprobado es un parámetro desnudo no se ejecuta una vez — se ejecuta una vez por miembro de la unión, y los resultados se vuelven a unir.

    Sigue el tipoVerificado por el compilador
    ExpresiónEach<string>
    Se resuelve astring[]
    ExpresiónEach<number>
    Se resuelve anever
    ExpresiónEach<string | number>
    Se resuelve astring[]
    ExpresiónWhole<string | number>
    Se resuelve anever

    Each comprueba cada miembro de la unión y combina los resultados; never desaparece. Whole comprueba toda la unión a la vez.

    Definición del tipo
    type Each<T> = T extends string ? T[] : never
    type Whole<T> = [T] extends [string] ? T[] : never

    Qué significa «desnudo»

    type Naked<T> = T extends string ? 'yes' : 'no'
    type A = Naked<string | number>   // 'yes' | 'no'  — ran twice
    
    type Wrapped<T> = [T] extends [string] ? 'yes' : 'no'
    type B = Wrapped<string | number>  // 'no' — ran once, on the whole union

    Desnudo significa que el parámetro está solo a la izquierda de extends, sin envolver en tupla, array, objeto ni promesa. Envolverlo en cualquier cosa desactiva la distribución y compara la unión como un todo.

    La distribución es cómo funciona el filtrado

    type MyExclude<T, U> = T extends U ? never : T
    MyExclude<'a' | 'b' | 'c', 'a'>   // 'b' | 'c'
    
    // Each member becomes never or survives; unioning drops the nevers,
    // because never is the identity element of union.

    Exclude, Extract y NonNullable son cada uno un condicional distributivo. El truco es que never desaparece de una unión, así que mapear los miembros no deseados a never los elimina gratis.

    Dos modismos que conviene memorizar

    // Force distribution, even when you do not need the test:
    type Each<K> = K extends K ? [K] : never
    
    // Suppress it, and detect the empty union:
    type IsNever<T> = [T] extends [never] ? true : false

    K extends K es trivialmente cierto y existe solo para distribuir. [T] extends [never] es la única forma fiable de preguntar «¿esto es never?», porque un condicional distributivo sobre never produce never y no llega a ninguna rama.

    El error más común

    // Intent: does T contain string?
    type HasString<T> = T extends string ? true : false
    
    HasString<string | number>  // boolean, not true
    // It distributed: true | false, which collapses to boolean.
    
    // Fixed: ask about the union as a whole.
    type HasString2<T> = [T] extends [string] ? true : false

    Un condicional que devuelve boolean en vez de true o false es casi siempre distribución no intencionada. La unión de true y false es boolean, y por eso el síntoma parece tan inocente.

    Ejercicio

    Elimina null de una unión — y deja que el condicional distribuya.

    Pruébalo

    2
    • NoNull<string | null>—
      → string
    • NoNull<null>—
      → never

    Para recordar

    Un parámetro desnudo se distribuye; envolverlo en [] lo detiene. Cuando un tipo devuelve misteriosamente boolean o never, la distribución es lo primero que hay que mirar.

    Se practica en