Function tail
Let's to see first what is tail
.
function tail<T extends any[]>([head, ...tail]: T): Tail<T> {
return tail as Tail<T>;
}
const array_tail = tail([1, 2, 3, 4]);
// out: [2,3,4]
It's really simple, a pure function that drops the head
of the array.
Now for build the type Tail
we will follow the same approach, we need to do a trick.
// TS: 3.0^
type Tail<T extends any[]> = ((...array: T) => any) extends (
head: any,
...tail: infer Tail_
) => any
? Tail_
: never;
As TS 3.0^ doesn't allow to use array expresions to deconstruct T
, we need to use a function with rest parameters and infer word to obtain the tail
.
The main restriction for use infer
is a conditional <left expr> extends <here infer a type> ? <access to infered type> : <otherwise>
. As the arrays are used for type rest parameters, I put T
in left hand of extends
just like that ((...array: T) => any)
and rigth hand I can deconstruct the array and infer what I'm interested me (head: any, ...tail: infer Tail_)
.
With TS 4.0^
we can simplify this definition, but the approach is the same.
Look at this.
// TS: 4.0^
type Tail<T extends any[]> = T extends [head: any, ...tail: infer Tail_]
? Tail_
: never;
๐ Currifying functions
The currification is a Functional Programming technique that transforms a function with multiple arguments in a function that takes arguments one by one until the last argument is provided and then returns the final result.
The term curry is in honor of Haskell Brooks Curry, he was the great matematician who created the Lambda Calculus ๐.
The cool thing about this, is that the currified functions allow to do partial application of functions.
Consider the next function.
// currified add function
const add = (a: number) => (b: number) => a + b;
// partial application with 10
const add10 = add(10);
// function: (b: number) => 10 + b
add10(1);
// out: 11
add10(5);
// out: 15
Instead of declare add as (a, b) => a + b
, add takes first a
, later takes b
and then do the calculation.
Now the idea is to create a type that takes a function and gives the same function but currified.
First we are going to create a Curry
type that bit to bit let us to improve.
type Length<T extends any[]> = T["length"];
type Head<T extends any[]> = T extends [] ? never : T[0];
type Curry<
F extends (...args: any[]) => any,
P extends any[] = Parameters<F>, // P: are the parameters of F
R = ReturnType<F> // R: the return type of F
> = Length<P> extends 0 | 1
? F
: (arg_0: Head<P>) => Curry<(...args: Tail<P>) => R>;
type F0 = Curry<() => Date>;
// type: () => Date
type F1 = Curry<(a: number) => Date>;
// type: (arg: number) => Date
type F2 = Curry<(a: number, b: string) => Date>;
// type: (arg: number) => (arg: string) => Date
First I decompose F
in parameters P
and return type R
. If the function has 0 or 1 arguments then F
is already currified, else takes the Head
of P
and with help Tail
previously declared, I defined the return in terms of itself Curry<(...args: Tail<P>)
. But this Curry type has some issues.
type F = Curry<(a?: string, b?: string, c?: string) => Date>;
// type: (a: string) => (b: string) => (c?: string) => Date
Now the parameters a
and b
aren't optional. This happens because Head
don't preserve the optionality.
type First = Head<[string?]>;
// type: string
To control this edge case we need to discard the tail without loss the optionality in the front of the array.
type Take1<T extends any[]> = Take1_<T>["type"];
interface Take1_<T extends any[]> {
type: T extends { 0: infer U } ? [U]
: T extends { 0?: infer U } ? [U?]
: never;
}
type T1 = Take1<[string?]>;
// type: [string?]
type T2 = Take1<[string]>;
// type: [string]
Then with this new type we can fix Curry
.
type Curry<
F extends (...args: any[]) => any,
P extends any[] = Parameters<F>, // P: are the parameters of F
R = ReturnType<F> // R: the return type of F
> =
Take1<Tail<P>> extends never
? F // F is () => any | (a: any) => any | (a: any, ...args: any[]) => any
: (...args: Take1<P>) => Curry<(...args: Tail<P>) => R>;
type F = Curry<(a?: string, b?: string, ...c: string[]) => Date>;
// type: (a?: string) => (b?: string, ...c: string[]) => Date
When Take1<Tail<P>>
is never
it means that P
is [] | [any] | any[]
and then is time to stop the recursion. Aftermore note how functions such as (a: any, ..args: any[]) => any
will not be currified, but we could do it if we wanted.
How would you implement a curry function for this type?
Top comments (0)