Saltar al contenido
webtype.orgDoce conceptos · 8/12

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.

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.

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