Búsqueda de sitios web

typescript - Definir un tipo de pares alternos


A partir de esta pregunta sobre Cómo definir un arreglo con tipos alternos en TypeScript?, he visto que es posible definir un tipo de arreglo donde los tipos deben seguir siempre un orden tal que sean válidos los siguientes para Alternating<T, U>:

[]
[T]
[T, U]
[T, U, T]
[T, U, T, U]

Además, cualquier longitud de elementos, siempre que estén en un orden tal que los elementos de tipo U siempre sean precedidos por un elemento de tipo T.

Pero la definición del tipo es un poco compleja, y como tal querría evitarla; mi caso de uso es un poco diferente, solo querría tener lo siguiente como válido dentro del sistema de tipos:

[T, U]
[T, U, T, U]

Hasta cualquier longitud de arreglo, siempre que todos los valores existan en un emparejamiento T, U - no arreglo vacío, no T 'colgante'.

¿Es esto posible? Había pensado en intentar algo como:

type AlternatingPairs<A, B> = [A, B, ...AlternatingPairs<A, B>];

Antes de darme cuenta de que no se pueden tener referencias circulares de esta manera en TypeScript.

Respuesta #1

Sé que esta implementación no es fácil de entender y puede que no sea útil para el OP, pero la dejo aquí ya que podría ayudar a otras personas:

type MAXIMUM_ALLOWED_BOUNDARY = 50

type Mapped<
    Tuple extends Array<unknown>,
    Result extends Array<unknown> = [],
    Count extends ReadonlyArray<number> = []
    > =
    (Count['length'] extends MAXIMUM_ALLOWED_BOUNDARY
        ? Result
        : (Tuple extends []
            ? []
            : (Result extends []
                ? Mapped<Tuple, Tuple, [...Count, 1]>
                : Mapped<Tuple, Result | [...Result, ...Tuple], [...Count, 1]>)
        )
    )



type Result = Mapped<[string, number]>

// 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24
type Test = Result['length']

Parque infantil

El tipo de utilidad Mapped simplemente crea una unión de todos los estados permitidos, como:

| [string, number]
| [stirng, number, string, number]
| [stirng, number, string, number, string, number]
| [string, number, ... ]

En cada iteración estoy incrementando el arreglo Count en 1. Así es como sé cuándo detener la iteración recursiva.

Puedes crear cualquier tipo de valores permitidos. Quiero decir, puedes usar triples en lugar de tupla. Ejemplo: Mapped<[string, number, number[]]>

Pregunta/respuestas relacionadas que puedes encontrar aquí y aquí. También puedes consultar mi artículo sobre cómo crear una tupla con longitud par y validarla.

ACTUALIZACIÓN: Aquí puedes encontrar una pregunta muy similar. Incluso creo que es un duplicado

Respuesta #2

Según la solicitud de @KyleMit, esta respuesta combina mis comentarios en un solo bloque formal

Si debes trabajar con una "tupla dinámica", recomiendo encarecidamente mirar la respuesta original en la pregunta enlazada. Aborda el problema al revés. Lo cual, considerando todo, es la única manera de hacer lo que OP pide estrictamente. No creas el tipo de pares alternados, validas si algo entra dentro del tipo de pares alternados.

En cambio, voy a hablar sobre por qué la creación de ese tipo es problemática y ofrecer una representación alternativa de dicho tipo de "pares alternos".

¿Por qué no?

El problema fundamental al crear un tipo de "pares alternos" (o cualquier tipo dinámico de "pares") es que un par es estrictamente una tupla. En teoría de tipos, una tupla es un tipo producto y, por lo tanto, memoriza cada uno de sus tipos residentes (posiblemente) distintos junto con su información posicional.

El tipo [T, U] se lee como "Un par donde T es el tipo del primer valor, y U es el tipo del segundo valor". El tipo [T, U, T] es completamente distinto de [T, U]. Aunque las tuplas de TypeScript son solo arreglos de JavaScript, la diferencia es sustancial en el sistema de tipos. Sencillamente no hay manera de tener un tipo de tupla dinámico, ya que un tipo producto es exacto y estático.

¿Qué es entonces un arreglo?

Un arreglo es una colección homogénea. En teoría de tipos, un "arreglo" (eh, es más como una lista cons pero bueno) solo necesita contener el tipo de sus elementos en general, no le importa sus posiciones, y no le importa su longitud (bueno, a menos que estemos hablando de tipos dependientes, pero eso es un tema para otro día). Observa cómo [1, "foo", True], cuando se usa como arreglo, tiene el tipo Array<number | string | boolean>. Su tipo de elementos en general es efectivamente number | string | boolean y no recuerda exactamente dónde están ubicados. Puedes agregar cualquier tipo que sea asignable a number | string | boolean a este arreglo.

Otra perspectiva

Quiero tomar un momento para retroceder todo. Yendo completamente al abstracto, el objetivo de OP es una "colección secuencial y dinámica de pares". Una colección secuencial y dinámica es un arreglo (no una tupla estática). Y un par es... una tupla. ¿Qué pasa si, en lugar de esto-

[T, U] o [T, U, T, U] o [T, U, T, U, T, U] ad infinitum

Emparejamos los pares para obtener-

[(T, U)] o [(T, U), (T, U)] o [(T, U), (T, U), (T, U)] hasta el infinito

NOTA: Eso no es sintaxis de JS/TS, usé paréntesis allí para mayor claridad, las tuplas se representan, por supuesto, con corchetes angulares en su lugar ([[T, U], [T, U]...]).

Homogeneidad - la única característica necesaria para tener una colección secuencial dinámica. Ese último tipo es simplemente Array<[T, U]> o [T, U][] - un arreglo de tuplas - un arreglo de pares - una colección secuencial dinámica de pares.

Ese es el enfoque que recomiendo sinceramente. Es mucho más fácil de entender y, probablemente, de mantener.

Datos irrelevantes

Ok, así que la afirmación "los arreglos son una colección homogénea, en teoría de tipos" es un poco mentira. Pero es solo una mentira de buena fe. Verás, los arreglos heterogéneos y bien tipados existen - en sistemas de tipos muy avanzados. Sin embargo, son.....difíciles de domar, y honestamente irrelevantes en el contexto de TypeScript. Pero ya sabes, en caso de que alguien encuentre esto interesante, aquí está Haskell con tipos de datos algebraicos generalizados y familias de tipos.

Vaya, esos son unos nombres difíciles.

Admito que no sabía nada de esto cuando hice los comentarios aquí, eso fue hace bastante meses. Me divertí demasiado con Haskell mientras tanto :)

Artículos relacionados