Tipando uma cadeia de funções
Como fazer sua função pipe ser fortemente tipada? Os conceitos por trás de tipos recursivos e cadeias de função
Contexto
Após a descoberta do reduce tipado, eu comecei a criar alguns desafios de tipo para poder ver até onde essa implementação resolve problemas do Typescript. Dessa vez me arrisquei a utilizar essa implementação para resolver o problema da função pipe.
tl;dr
Too long; didn't read
Caso você só queira somente olhar o resultado, você pode observar abaixo. Não se esqueça de instalar a dependência ts-toolbelt. Mas vale avisar que existe um limite para a tipagem desse modo, já que não é possível fazer uma extensa inferência. Isso é comentado nas implementações abaixo sobre o ramda e o lodash. Esse resultado é um resultado um pouco genérico e pode apresentar problemas em algumas implementações. Caso você queira uma solução um pouco mais robusta, você pode seguir com a leitura.
import { import L
L, import N
N } from "ts-toolbelt";
type type Fn = (a: any[]) => any
Fn = (a: any[]
a: any[]) => any;
type type Func = (...a: any[]) => any
Func = (...a: any[]
a: any[]) => any
type type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0> = Fns["length"] extends C ? Acc : PipeArgs<Fns, Fns[C], L.Merge<Acc, [(p: ReturnType<Func>) => ReturnType<Fns[C]>]>, N.Add<C, 1>>
PipeArgs<function (type parameter) Fns in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
Fns extends readonly type Fn = (a: any[]) => any
Fn[], function (type parameter) Func in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
Func extends type Fn = (a: any[]) => any
Fn, function (type parameter) Acc in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
Acc extends readonly type Fn = (a: any[]) => any
Fn[] = [], function (type parameter) C in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
C extends number = 0> = function (type parameter) Fns in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
Fns["length"] extends function (type parameter) C in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
C
? function (type parameter) Acc in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
Acc
: type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0> = Fns["length"] extends C ? Acc : PipeArgs<Fns, Fns[C], L.Merge<Acc, [(p: ReturnType<Func>) => ReturnType<Fns[C]>]>, N.Add<C, 1>>
PipeArgs<function (type parameter) Fns in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
Fns, function (type parameter) Fns in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
Fns[function (type parameter) C in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
C], import L
L.type Merge<L extends List, L1 extends List, depth extends Depth = "flat", ignore extends object = BuiltIn, fill extends unknown = undefined> = Merge<L, L1, depth, ignore, fill> extends L.List ? Merge<L, L1, depth, ignore, fill> : L.List
export Merge
Accurately merge the fields of `L` with the ones of `L1`. It is
equivalent to the spread operator in JavaScript. [[Union]]s and [[Optional]]
fields will be handled gracefully.
(⚠️ needs `--strictNullChecks` enabled)Merge<function (type parameter) Acc in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
Acc, [(p: ReturnType<Func>
p: type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function typeReturnType<function (type parameter) Func in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
Func>) => type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function typeReturnType<function (type parameter) Fns in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
Fns[function (type parameter) C in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
C]>]>, import N
N.type Add<N1 extends number, N2 extends number> = N1 extends unknown ? N2 extends unknown ? _Add<IterationOf<N1>, IterationOf<N2>>[0] : never : never
export Add
Add a [[Number]] to another oneAdd<function (type parameter) C in type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0>
C, 1>>;
type type PipeReturn<First extends Fn, Last extends Fn> = (...params: Parameters<First>) => ReturnType<Last>
PipeReturn<function (type parameter) First in type PipeReturn<First extends Fn, Last extends Fn>
First extends type Fn = (a: any[]) => any
Fn, function (type parameter) Last in type PipeReturn<First extends Fn, Last extends Fn>
Last extends type Fn = (a: any[]) => any
Fn> = (...params: Parameters<First>
params: type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tupleParameters<function (type parameter) First in type PipeReturn<First extends Fn, Last extends Fn>
First>) => type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function typeReturnType<function (type parameter) Last in type PipeReturn<First extends Fn, Last extends Fn>
Last>;
export const const pipe: <A extends Func, T extends readonly Fn[]>(a: A, ...fns: PipeArgs<T, A>) => PipeReturn<A, L.Last<T>>
pipe = <function (type parameter) A in <A extends Func, T extends readonly Fn[]>(a: A, ...fns: PipeArgs<T, A>): PipeReturn<A, L.Last<T>>
A extends type Func = (...a: any[]) => any
Func, function (type parameter) T in <A extends Func, T extends readonly Fn[]>(a: A, ...fns: PipeArgs<T, A>): PipeReturn<A, L.Last<T>>
T extends readonly type Fn = (a: any[]) => any
Fn[]>(a: A extends Func
a: function (type parameter) A in <A extends Func, T extends readonly Fn[]>(a: A, ...fns: PipeArgs<T, A>): PipeReturn<A, L.Last<T>>
A, ...fns: PipeArgs<T, A, [], 0>
fns: type PipeArgs<Fns extends readonly Fn[], Func extends Fn, Acc extends readonly Fn[] = [], C extends number = 0> = Fns["length"] extends C ? Acc : PipeArgs<Fns, Fns[C], L.Merge<Acc, [(p: ReturnType<Func>) => ReturnType<Fns[C]>]>, N.Add<C, 1>>
PipeArgs<function (type parameter) T in <A extends Func, T extends readonly Fn[]>(a: A, ...fns: PipeArgs<T, A>): PipeReturn<A, L.Last<T>>
T, function (type parameter) A in <A extends Func, T extends readonly Fn[]>(a: A, ...fns: PipeArgs<T, A>): PipeReturn<A, L.Last<T>>
A>): type PipeReturn<First extends Fn, Last extends Fn> = (...params: Parameters<First>) => ReturnType<Last>
PipeReturn<function (type parameter) A in <A extends Func, T extends readonly Fn[]>(a: A, ...fns: PipeArgs<T, A>): PipeReturn<A, L.Last<T>>
A, import L
L.type Last<L extends List> = L[L.Length<L.Tail<L>>]
export Last
Get the last entry of `L`Last<function (type parameter) T in <A extends Func, T extends readonly Fn[]>(a: A, ...fns: PipeArgs<T, A>): PipeReturn<A, L.Last<T>>
T>> =>
(fns: PipeArgs<T, A, [], 0>
fns as type Fn = (a: any[]) => any
Fn[]).Array<Fn>.reduce(callbackfn: (previousValue: Fn, currentValue: Fn, currentIndex: number, array: Fn[]) => Fn, initialValue: Fn): Fn (+2 overloads)
Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.reduce(
(f: Fn
f: type Fn = (a: any[]) => any
Fn, g: Fn
g: type Fn = (a: any[]) => any
Fn) =>
(args: any
args: any) =>
g: (a: any[]) => any
g(f: (a: any[]) => any
f(args: any
args)), (...args: unknown[]
args: unknown[]) => a: A
(...a: any[]) => any
a(...args: unknown[]
args)) as any;
Pipe
Como dito no artigo de programação funcional, esse artigo visa explicar melhor a tipagem da função pipe
e o porque dela ser tão complicada de tipar.
Antes de apresentar o código (que já está no tl;dr), vamos ver algumas implementações da função pipe
ramda
Ramda é uma biblioteca que busca trazer uma forma mais funcional para o Javascript, e depois para o Typescript. Olhando no repositório que contém os @types
, podemos ver a seguinte implementação da função pipe
do ramda. Caso você prefira, pode olhar a implementação direto no GitHub.
export function pipe<TArgs extends any[], R1, R2, R3, R4, R5, R6, R7, TResult>(
...funcs: [
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
f3: (a: R2) => R3,
f4: (a: R3) => R4,
f5: (a: R4) => R5,
f6: (a: R5) => R6,
f7: (a: R6) => R7,
...func: Array<(a: any) => any>,
fnLast: (a: any) => TResult,
]
): (...args: TArgs) => TResult; // fallback overload if number of piped functions greater than 7
export function pipe<TArgs extends any[], R1, R2, R3, R4, R5, R6, R7>(
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
f3: (a: R2) => R3,
f4: (a: R3) => R4,
f5: (a: R4) => R5,
f6: (a: R5) => R6,
f7: (a: R6) => R7,
): (...args: TArgs) => R7;
export function pipe<TArgs extends any[], R1, R2, R3, R4, R5, R6>(
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
f3: (a: R2) => R3,
f4: (a: R3) => R4,
f5: (a: R4) => R5,
f6: (a: R5) => R6,
): (...args: TArgs) => R6;
export function pipe<TArgs extends any[], R1, R2, R3, R4, R5>(
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
f3: (a: R2) => R3,
f4: (a: R3) => R4,
f5: (a: R4) => R5,
): (...args: TArgs) => R5;
export function pipe<TArgs extends any[], R1, R2, R3, R4>(
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
f3: (a: R2) => R3,
f4: (a: R3) => R4,
): (...args: TArgs) => R4;
export function pipe<TArgs extends any[], R1, R2, R3>(
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
f3: (a: R2) => R3,
): (...args: TArgs) => R3;
export function pipe<TArgs extends any[], R1, R2>(
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
): (...args: TArgs) => R2;
export function pipe<TArgs extends any[], R1>(f1: (...args: TArgs) => R1): (...args: TArgs) => R1;
É um pouco complicado de entender devido à sobrecarga de método utilizada no código. E é bem interessante lembrar disso porque outra biblioteca bastante famosa, o lodash também faz o uso da mesma técnica.
lodash
Como dito antes, aqui está o código do Lodash. E como podemos ver, ambos fazem o uso de sobrecarga de método para resolver o problema da função pipe.
interface LodashFlow {
<A extends any[], R1, R2, R3, R4, R5, R6, R7>(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (...args: A) => R7;
<A extends any[], R1, R2, R3, R4, R5, R6, R7>(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7, ...func: Array<lodash.Many<(a: any) => any>>): (...args: A) => any;
<A extends any[], R1, R2, R3, R4, R5, R6>(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (...args: A) => R6;
<A extends any[], R1, R2, R3, R4, R5>(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (...args: A) => R5;
<A extends any[], R1, R2, R3, R4>(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (...args: A) => R4;
<A extends any[], R1, R2, R3>(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (...args: A) => R3;
<A extends any[], R1, R2>(f1: (...args: A) => R1, f2: (a: R1) => R2): (...args: A) => R2;
(...func: Array<lodash.Many<(...args: any[]) => any>>): (...args: any[]) => any;
}
lodash vs ramda
Como você pode observer no código, o lodash e o ramda possuem um número finito de funções que podem ser encadeadas. Tudo bem que 7 funções para um pipe pode ser um exagero tremendo, mas caso você precise de extender isso ou apenas se desafiar a como resolver um problema de tipagem, você pode resolver utilizando os tipos recursivos + reduce.
Pipe tipado + Type Reduce
Antes de tudo, é válido lembrar que a inferência do pipe acaba não sendo extensa, devido à limitação na inferência no rest parameters.
Se você leu o artigo de type reduce, você terá um pouco mais de contexto de como funciona a lógica desse tipo. Infelizmente o Typescript não nos ajuda na inferência dos tipos através do rest-parameter, como dito anteriormente. Porém, para contornar esse problema nós vamos utilizar algumas artemanhas da linguagem para poder resolver esse problema.
Rest Parameter e o problema de inferência
Como nós visamos receber pelo menos duas funções, os primeiros dois argumentos precisam ser especificados. Do terceiro em diante nós vamos aceitar quaisquer funções, não importa se sejam 10 ou nenhuma.
O problema do rest parameter nesse caso é que precisamos aplicar uma regra nos parâmetros, sendo que eles não foram recebidos e tratados da forma devida da linguagem. Alterar os parâmetros da função diretamente no construtor da função acabam confundindo o nosso type system e jogando toda a inferência para o lado any
da força.
Tendo isso em mente, ao invés de testar os parâmetros em sua entrada, por que nós não podemos modificar a saída em caso da entrada estar errada? Quê?????????????????????????????
Fica tranquilo, vamos entender um pouco melhor essa frase.
- Iremos receber 3 parâmetros na nossa função
first
,second
e umrest
, sendo esse um rest parameter (N funções permitidas). 1. O parâmetrofirst
precisa extender(...params: any[]) => any
. Pois, a entrada pode ter N argumentos. 2. Os parâmetrossecond
erest
precisam extender(a: any) => any
. Como todas as funções possuem somente um retorno, essas funções só podem ter uma entrada. - Os parâmetros serão recebidos e entendidos pelo Typescript, sem precisar realizar nenhuma operação, com isso a inferência do tipo será preservada e nossas funções poderão ser devidamente tratadas
- O retorno da nossa função
pipe
será um retorno customizado baseado na entrada.- Caso todas as funções respeitem a regra O tipo de entrada da função é o mesmo do retorno da função anterior, nós poderemos retornar a execução da função pipe de forma adequada
- Caso uma das funções não respeite a regra, iremos criar um objeto customizado e não permitir que pipe retorne uma função.
- Para criar o retorno, iremos criar os argumentos da nossa função pipe normalmente
- Após a criação, vamos comparar as entradas e saídas com as funções passadas
- Caso uma das funções tenha erro, iremos marca-lá como
false
e armazenar o seu index - Todas as funções que possuirem erros serão armazenadas num acumulador para serem tratadas como erro
- Por fim, teremos um tipo que irá converter todas as funções erradas em objetos de erro
- A função pipe irá deixar de retornar uma função e irá retornar um objeto com os erros presentes nas funções
Code
A lógica pode ser um pouco complicada, mas que tal a gente ir comentando o código para facilitar o entendimento? Se você preferir, pode olhar o playground
// Utilitários para que possamos iterar os arrays
import { import F
F, import N
N } from "ts-toolbelt";
// funções que recebem somente um argumento
type type Unary = (a: any) => any
Unary = (a: any
a: any) => any;
// funções que recebem N argumentos
type type Func = (...a: any[]) => any
Func = (...a: any[]
a: any[]) => any;
type type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0> = Fns["length"] extends I ? Acc : I extends 0 ? Pipe<Fns[I], Fns, [...Acc, (...params: Parameters<First>) => ReturnType<First>], N.Add<I, 1>> : Pipe<...>
Pipe<
// primeira função de referência pipe
function (type parameter) First in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
First extends type Func = (...a: any[]) => any
Func,
// todas as funções passadas na nossa função pipe
function (type parameter) Fns in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Fns extends interface Array<T>
Array<type Func = (...a: any[]) => any
Func | type Unary = (a: any) => any
Unary>,
// acumulador de argumentos
function (type parameter) Acc in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Acc extends type Func = (...a: any[]) => any
Func[] = [],
// contador do nosso tipo recursivo
function (type parameter) I in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
I extends number = 0
// Aqui testamos a condição para saber se chegamos ao fim do array
> = function (type parameter) Fns in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Fns["length"] extends function (type parameter) I in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
I
// Caso o tamanho do array seja o mesmo do contador, retornamos o acumulador
? function (type parameter) Acc in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Acc
: (
// Nesse ponto testamos para saber se é a primeira função de pipe
function (type parameter) I in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
I extends 0 ?
// Caso seja a primeira função, utilizamos os parâmetros recebidos por ela
type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0> = Fns["length"] extends I ? Acc : I extends 0 ? Pipe<Fns[I], Fns, [...Acc, (...params: Parameters<First>) => ReturnType<First>], N.Add<I, 1>> : Pipe<...>
Pipe<function (type parameter) Fns in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Fns[function (type parameter) I in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
I], function (type parameter) Fns in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Fns, [...function (type parameter) Acc in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Acc, (...params: Parameters<First>
params: type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tupleParameters<function (type parameter) First in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
First>) => type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function typeReturnType<function (type parameter) First in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
First>], import N
N.type Add<N1 extends number, N2 extends number> = N1 extends unknown ? N2 extends unknown ? _Add<IterationOf<N1>, IterationOf<N2>>[0] : never : never
export Add
Add a [[Number]] to another oneAdd<function (type parameter) I in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
I, 1>>
// Caso não seja, utilizamos o retorno da função anterior como parâmetro
: type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0> = Fns["length"] extends I ? Acc : I extends 0 ? Pipe<Fns[I], Fns, [...Acc, (...params: Parameters<First>) => ReturnType<First>], N.Add<I, 1>> : Pipe<...>
Pipe<function (type parameter) Fns in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Fns[function (type parameter) I in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
I], function (type parameter) Fns in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Fns, [...function (type parameter) Acc in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Acc, (param: ReturnType<Fns[N.Sub<I, 1>]>
param: type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function typeReturnType<function (type parameter) Fns in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Fns[import N
N.type Sub<N1 extends number, N2 extends number> = N1 extends unknown ? N2 extends unknown ? _Sub<IterationOf<N1>, IterationOf<N2>>[0] : never : never
export Sub
Subtract a [[Number]] from another oneSub<function (type parameter) I in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
I, 1>]>) => type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function typeReturnType<function (type parameter) Fns in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
Fns[function (type parameter) I in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
I]>], import N
N.type Add<N1 extends number, N2 extends number> = N1 extends unknown ? N2 extends unknown ? _Add<IterationOf<N1>, IterationOf<N2>>[0] : never : never
export Add
Add a [[Number]] to another oneAdd<function (type parameter) I in type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0>
I, 1>>
)
type type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0> = Fns["length"] extends I ? Acc : ExtractInfo<Fns, Transform, [...Acc, Parameters<Fns[I]> extends Parameters<Transform[I]> ? unknown : Fns[I]], N.Add<...>>
ExtractInfo<
// Todas as funções originais
function (type parameter) Fns in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Fns extends type Func = (...a: any[]) => any
Func[],
// Todas as funções transformadas
function (type parameter) Transform in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Transform extends type Func = (...a: any[]) => any
Func[],
// acumulador de erros
function (type parameter) Acc in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Acc extends any[] = [],
// contador
function (type parameter) I in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
I extends number = 0
// Fim da recursão
> = function (type parameter) Fns in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Fns["length"] extends function (type parameter) I in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
I
? function (type parameter) Acc in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Acc
// recursão
: type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0> = Fns["length"] extends I ? Acc : ExtractInfo<Fns, Transform, [...Acc, Parameters<Fns[I]> extends Parameters<Transform[I]> ? unknown : Fns[I]], N.Add<...>>
ExtractInfo<function (type parameter) Fns in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Fns, function (type parameter) Transform in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Transform, [
...function (type parameter) Acc in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Acc,
(
// Aqui é feito um teste para ver se os parâmetros
// da função transformada são iguais aos da função
// original. Caso sejam iguais, um unknown é retornado.
// Caso sejam diferentes, retornamos a própria função
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tupleParameters<function (type parameter) Fns in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Fns[function (type parameter) I in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
I]> extends type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tupleParameters<function (type parameter) Transform in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Transform[function (type parameter) I in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
I]> ? unknown : function (type parameter) Fns in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
Fns[function (type parameter) I in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
I]
)
], import N
N.type Add<N1 extends number, N2 extends number> = N1 extends unknown ? N2 extends unknown ? _Add<IterationOf<N1>, IterationOf<N2>>[0] : never : never
export Add
Add a [[Number]] to another oneAdd<function (type parameter) I in type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0>
I, 1>>
// Aqui é um tipo recursivo utilitário para identificar se algum item do array
// é uma função. Ele irá nos ajudar a identificar os erros
type type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0> = Result extends true ? true : Tests["length"] extends I ? false : OneIsFunction<Tests, Tests[I] extends Func ? true : false, N.Add<I, 1>>
OneIsFunction<function (type parameter) Tests in type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0>
Tests extends unknown[], function (type parameter) Result in type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0>
Result extends boolean = false, function (type parameter) I in type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0>
I extends number = 0> = function (type parameter) Result in type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0>
Result extends true ? true :
function (type parameter) Tests in type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0>
Tests["length"] extends function (type parameter) I in type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0>
I ? false
: type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0> = Result extends true ? true : Tests["length"] extends I ? false : OneIsFunction<Tests, Tests[I] extends Func ? true : false, N.Add<I, 1>>
OneIsFunction<function (type parameter) Tests in type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0>
Tests, function (type parameter) Tests in type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0>
Tests[function (type parameter) I in type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0>
I] extends type Func = (...a: any[]) => any
Func ? true : false, import N
N.type Add<N1 extends number, N2 extends number> = N1 extends unknown ? N2 extends unknown ? _Add<IterationOf<N1>, IterationOf<N2>>[0] : never : never
export Add
Add a [[Number]] to another oneAdd<function (type parameter) I in type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0>
I, 1>>
// Aqui é um outro tipo recursivo que nos ajuda a identificar o index
// da função com erro.
type type FunctionIndexError<Tests extends unknown[], I extends number = 0> = Tests[I] extends Func ? I : FunctionIndexError<Tests, N.Add<I, 1>>
FunctionIndexError<function (type parameter) Tests in type FunctionIndexError<Tests extends unknown[], I extends number = 0>
Tests extends unknown[], function (type parameter) I in type FunctionIndexError<Tests extends unknown[], I extends number = 0>
I extends number = 0> = function (type parameter) Tests in type FunctionIndexError<Tests extends unknown[], I extends number = 0>
Tests[function (type parameter) I in type FunctionIndexError<Tests extends unknown[], I extends number = 0>
I] extends type Func = (...a: any[]) => any
Func ? function (type parameter) I in type FunctionIndexError<Tests extends unknown[], I extends number = 0>
I :
type FunctionIndexError<Tests extends unknown[], I extends number = 0> = Tests[I] extends Func ? I : FunctionIndexError<Tests, N.Add<I, 1>>
FunctionIndexError<function (type parameter) Tests in type FunctionIndexError<Tests extends unknown[], I extends number = 0>
Tests, import N
N.type Add<N1 extends number, N2 extends number> = N1 extends unknown ? N2 extends unknown ? _Add<IterationOf<N1>, IterationOf<N2>>[0] : never : never
export Add
Add a [[Number]] to another oneAdd<function (type parameter) I in type FunctionIndexError<Tests extends unknown[], I extends number = 0>
I, 1>>
// Por último, temos o tipo que irá agregar toda a lógica explicada
// Caso exista alguma função no nosso array de `Tests`, iremos retornar um objeto de erro.
// Se tudo estiver certo, retornamos a assinatura correta do retorno do pipe.
type type CreatePipe<Fns extends Func[], Tests extends unknown[]> = (Tests["length"] extends 0 ? false : OneIsFunction<Tests, Tests[0] extends Func ? true : false, 1>) extends true ? {
message: "wrong-function";
errorAt: FunctionIndexError<Tests>;
functions: Tests;
} : (...params: Parameters<Fns[0]>) => ReturnType<Fns[N.Sub<Fns["length"], 1>]>
CreatePipe<function (type parameter) Fns in type CreatePipe<Fns extends Func[], Tests extends unknown[]>
Fns extends type Func = (...a: any[]) => any
Func[], function (type parameter) Tests in type CreatePipe<Fns extends Func[], Tests extends unknown[]>
Tests extends unknown[]> = type OneIsFunction<Tests extends unknown[], Result extends boolean = false, I extends number = 0> = Result extends true ? true : Tests["length"] extends I ? false : OneIsFunction<Tests, Tests[I] extends Func ? true : false, N.Add<I, 1>>
OneIsFunction<function (type parameter) Tests in type CreatePipe<Fns extends Func[], Tests extends unknown[]>
Tests> extends true ? {
message: "wrong-function"
message: "wrong-function";
errorAt: FunctionIndexError<Tests, 0>
errorAt: type FunctionIndexError<Tests extends unknown[], I extends number = 0> = Tests[I] extends Func ? I : FunctionIndexError<Tests, N.Add<I, 1>>
FunctionIndexError<function (type parameter) Tests in type CreatePipe<Fns extends Func[], Tests extends unknown[]>
Tests>
functions: Tests extends unknown[]
functions: function (type parameter) Tests in type CreatePipe<Fns extends Func[], Tests extends unknown[]>
Tests
// Podemos ler:
// `Retorne uma função que os parâmetros são referentes
// aos parâmetros da primeira função e o retorno seja
// o tipo de retorno da última função`
} : ((...params: Parameters<Fns[0]>
params: type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tupleParameters<function (type parameter) Fns in type CreatePipe<Fns extends Func[], Tests extends unknown[]>
Fns[0]>) => type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function typeReturnType<function (type parameter) Fns in type CreatePipe<Fns extends Func[], Tests extends unknown[]>
Fns[import N
N.type Sub<N1 extends number, N2 extends number> = N1 extends unknown ? N2 extends unknown ? _Sub<IterationOf<N1>, IterationOf<N2>>[0] : never : never
export Sub
Subtract a [[Number]] from another oneSub<function (type parameter) Fns in type CreatePipe<Fns extends Func[], Tests extends unknown[]>
Fns["length"], 1>]>)
// Finalmente temos a nossa implementação de função pipe
const const pipe: <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest) => CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
pipe = <function (type parameter) First in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
First extends type Func = (...a: any[]) => any
Func, function (type parameter) Second in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
Second extends type Unary = (a: any) => any
Unary, function (type parameter) Rest in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
Rest extends import F
F.type Narrow<A extends unknown> = A extends [] ? A : NarrowRaw<A>
export Narrow
Prevent type widening on generic function parametersNarrow<type Unary = (a: any) => any
Unary[]>>(first: First extends Func
first: function (type parameter) First in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
First, second: Second extends Unary
second: function (type parameter) Second in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
Second, ...rest: Rest extends F.Narrow<Unary[]>
rest: function (type parameter) Rest in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
Rest):
type CreatePipe<Fns extends Func[], Tests extends unknown[]> = (Tests["length"] extends 0 ? false : OneIsFunction<Tests, Tests[0] extends Func ? true : false, 1>) extends true ? {
message: "wrong-function";
errorAt: FunctionIndexError<Tests>;
functions: Tests;
} : (...params: Parameters<Fns[0]>) => ReturnType<Fns[N.Sub<Fns["length"], 1>]>
CreatePipe<type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0> = Fns["length"] extends I ? Acc : I extends 0 ? Pipe<Fns[I], Fns, [...Acc, (...params: Parameters<First>) => ReturnType<First>], N.Add<I, 1>> : Pipe<...>
Pipe<function (type parameter) First in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
First, [function (type parameter) First in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
First, function (type parameter) Second in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
Second, ...function (type parameter) Rest in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
Rest]>, type ExtractInfo<Fns extends Func[], Transform extends Func[], Acc extends any[] = [], I extends number = 0> = Fns["length"] extends I ? Acc : ExtractInfo<Fns, Transform, [...Acc, Parameters<Fns[I]> extends Parameters<Transform[I]> ? unknown : Fns[I]], N.Add<...>>
ExtractInfo<[function (type parameter) First in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
First, function (type parameter) Second in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
Second, ...function (type parameter) Rest in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
Rest], type Pipe<First extends Func, Fns extends Array<Func | Unary>, Acc extends Func[] = [], I extends number = 0> = Fns["length"] extends I ? Acc : I extends 0 ? Pipe<Fns[I], Fns, [...Acc, (...params: Parameters<First>) => ReturnType<First>], N.Add<I, 1>> : Pipe<...>
Pipe<function (type parameter) First in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
First, [function (type parameter) First in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
First, function (type parameter) Second in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
Second, ...function (type parameter) Rest in <First extends Func, Second extends Unary, Rest extends F.Narrow<Unary[]>>(first: First, second: Second, ...rest: Rest): CreatePipe<Pipe<First, [First, Second, ...Rest]>, ExtractInfo<[First, Second, ...Rest], Pipe<First, [First, Second, ...Rest]>>>
Rest]>>> => ([first: First extends Func
first, second: Second extends Unary
second, ...rest: Rest extends F.Narrow<Unary[]>
rest] as type Func = (...a: any[]) => any
Func[]).Array<Func>.reduce(callbackfn: (previousValue: Func, currentValue: Func, currentIndex: number, array: Func[]) => Func): Func (+2 overloads)
Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.reduce((acc: Func
acc, fn: Func
fn) => (...args: any[]
args: any[]) => fn: (...a: any[]) => any
fn(acc: (...a: any[]) => any
acc(...args: any[]
args))) as any;
// Aqui um pequeno teste
const const sum: (a: number, b: number) => number
sum = (a: number
a: number, b: number
b: number) => {
var console: Console
console.Console.log(...data: any[]): void
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log({ a: number
a, b: number
b })
return a: number
a + b: number
b
}
const const pow2: (a: number) => number
pow2 = (a: number
a: number) => var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.Math.Math.pow(x: number, y: number): number
Returns the value of a base expression taken to a specified power.pow(a: number
a, 2)
const const mutateStringToArray: (a: number, b: number) => ReturnType<(param: number) => ReturnType<(a: number) => number>>
mutateStringToArray = const pipe: <(a: number, b: number) => number, (a: number) => number, []>(first: (a: number, b: number) => number, second: (a: number) => number) => CreatePipe<Pipe<(a: number, b: number) => number, [(a: number, b: number) => number, (a: number) => number, ...[]]>, ExtractInfo<[(a: number, b: number) => number, (a: number) => number, ...[]], Pipe<(a: number, b: number) => number, [(a: number, b: number) => number, (a: number) => number, ...[]]>>>
pipe(
const sum: (a: number, b: number) => number
sum, const pow2: (a: number) => number
pow2
);
const const result: number
result = const mutateStringToArray: (a: number, b: number) => ReturnType<(param: number) => ReturnType<(a: number) => number>>
mutateStringToArray(2, 2)
var console: Console
console.Console.log(...data: any[]): void
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(const result: number
result)
Conclusão
Esse tipo deu trabalho, mas conseguimos e ainda descobrimos uma técnica interessante sobre como debuggar os nossos tipos, visto que agora nossa função pipe pode nos alertar sobre o index errado e qual a assinatura da função errada. Por hoje é só isso tudão. Espero que tenham gostado e até a próxima.