¿TypeScript proporciona una forma de evitar este tipo de explosión de sobrecarga de métodos y, al hacerlo, proporciona seguridad de tipos a un número ilimitado de varargs?

type Operator<FROM, TO> = (source: Stream<FROM>) => Stream<TO>

class Stream<V> {
    // ...

    pipe<A>(operator: Operator<VALUE, A>): Stream<A>
    pipe<A, B>(op1: Operator<VALUE, A>, op2: Operator<A, B>): Stream<B>
    pipe<A, B, C>(op1: Operator<VALUE, A>, op2: Operator<A, B>, op3: Operator<B, C>): Stream<C>
    pipe<A, B, C, D>(op1: Operator<VALUE, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>): Stream<D>
    pipe<A, B, C, D, E>(op1: Operator<VALUE, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>): Stream<E>
    pipe<A, B, C, D, E, F>(op1: Operator<VALUE, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>): Stream<F>
    pipe<A, B, C, D, E, F, G>(op1: Operator<VALUE, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>, op7: Operator<F, G>): Stream<G>
    pipe<A, B, C, D, E, F, G, H>(op1: Operator<VALUE, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>, op7: Operator<F, G>, op8: Operator<G, H>): Stream<H>
    pipe<A, B, C, D, E, F, G, H, I>(op1: Operator<VALUE, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>, op7: Operator<F, G>, op8: Operator<G, H>, op9: Operator<H, I>): Stream<I>
    pipe<A, B, C, D, E, F, G, H, I, J>(op1: Operator<VALUE, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>, op7: Operator<F, G>, op8: Operator<G, H>, op9: Operator<H, I>, op10: Operator<I, J>): Stream<J>
    pipe<A, B, C, D, E, F, G, H, I, J, K>(op1: Operator<VALUE, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>, op7: Operator<F, G>, op8: Operator<G, H>, op9: Operator<H, I>, op10: Operator<I, J>, ...restOps: Operator<unknown, unknown>[]): Stream<K>

    pipe<TO_VALUE>(operator: Operator<VALUE, unknown>, ...restOperators: Operator<unknown, unknown>[]): Stream<TO_VALUE> {
        return restOperators.reduce((stream, operator) => operator(stream), this as Stream<unknown>) as Stream<TO_VALUE>
    }
}

Tenga en cuenta que el tipo de salida de cada Operator es el siguiente tipo de entrada de Operator.

2
Distortum 22 ago. 2020 a las 19:38

1 respuesta

La mejor respuesta

Es posible.

Aquí hay un ejemplo de trabajo que requiere tipos de tuplas de TypeScript 4. La idea es usar una matriz genérica T para realizar un seguimiento de cada tipo consecutivo y asegurarse de que los operadores estén de acuerdo.

Basé esta respuesta en este comentario de @jcalz.

type Operator<FROM, TO> = (source: Stream<FROM>) => Stream<TO>

type Prev<T extends any[], K, D> = K extends keyof [ D, ...T ] ? [ D, ...T ][K] : never;
type Operators<VALUE, T extends any[]> = {
    [K in keyof T]: Operator<Prev<T, K, VALUE>, T[K]>
};
type PipeResult<T extends any[]> = T extends [ ...infer _, infer U ] ? Stream<U> : never;

class Stream<VALUE> {
    x?: VALUE; // Needed to make sure Stream<X> doesn't always extend Stream<Y>.

    pipe<T extends any[]>(...operators: Operators<VALUE, T>): PipeResult<T> {
        return operators.reduce<Stream<unknown>>((stream, operator) => operator(stream), this) as PipeResult<T>;
    }
}

Pruebas:

const OpA: Operator<number, 'hi'> = null as any;
const OpB: Operator<string, 'hello'> = null as any;
const OpC: Operator<string, Date> = null as any;

const OpD: Operator<1521, string> = null as any;
const OpE: Operator<5, 6> = null as any;
const OpF: Operator<6, 7> = null as any;


const NumberStream = new Stream<number>();

// x0: Stream<Date>
const x0 = NumberStream.pipe(OpA, OpB, OpC);
// x1: Stream<"hello">
const x1 = NumberStream.pipe(
    OpA, OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB,
    OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB,
    OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB);

// Error on arg 3.
const x2 = NumberStream.pipe(OpA, OpC, OpC);
// Error on arg 1.
const x3 = NumberStream.pipe(OpD, OpE, OpF);

Enlace de juegos


Vieja mala respuesta: Zona de juegos Enlace

3
Mingwei Samuel 8 sep. 2020 a las 18:09