TS type gymnastic book nuggets schedule until April, a little late…

So I put out one of the routines as an article, you can feel it in advance, when the time will also be set up as a small book of trial reading chapter.

This routine is called array length count, is to use array length to achieve addition, subtraction, multiplication and division, all kinds of counting, is one of the six routines in the most coquacious.

Here is the text (small volume) :

Routine 4: Array length count

The TypeScript type system isn’t Turing-complete and can write all kinds of logic, but it doesn’t seem to find any numeric logic.

Yes, the logic of numerical correlation is convoluted, and I’ve picked it out separately, and that’s what this section is about.

This is the fourth trick of type gymnastics: count the array lengths.

Array length counts

The TypeScript type system does not have addition, subtraction, multiplication and division operators.

I don’t know if you noticed that the array type takes length as a number.

Such as:

We can construct arrays of different lengths and then take length, which is a numeric operation.

There are no addition, subtraction, multiplication and division operators in the TypeScript type system, but numerical calculations can be done by constructing different arrays and then taking length, translating the addition, subtraction, multiplication and division of values into array extraction and construction.

This is arguably the most troublesome point in type gymnastics, requiring some mental shifts to get around this bend.

Let’s do some real life examples to grasp it.

The array length implements addition, subtraction, multiplication and division

Add

We know that numeric computation is converted to an operation on an array type, so the implementation of addition is easy to think of:

Construct two arrays and merge them into one, taking length.

For example, 3 + 2, construct an array type of length 3, construct an array type of length 2, and combine them into an array, take length.

How long an array can be constructed is undefined, and requires recursive construction, which we implemented:

type BuildArray<
    Length extends number, 
    Ele = unknown, 
    Arr extends unknown[] = []
> = Arr['length'] extends Length 
        ? Arr 
        : BuildArray<Length, Ele, [...Arr, Ele]>;
Copy the code

The type argument Length is the Length of the array to construct. The type parameter Ele is an array element and defaults to unknown. The type argument Arr is the constructed array. The default is [].

If the Length of Arr reaches Length, the constructed Arr is returned, otherwise the recursive construction continues.

We can add an array based on it:

type Add<Num1 extends number, Num2 extends number> = 
    [...BuildArray<Num1>,...BuildArray<Num2>]['length'];
Copy the code

Let’s test with a larger number:

It turned out to be right.

Have a try

In this way, we implement addition by constructing an array of length.

Subtract

Addition is constructing an array, so how do you do subtraction?

Subtracting is removing part of a value, and it’s easy to imagine doing that by extracting an array type.

For example, 3 is the array type of [unknown, unknown, unknown]. After extracting 2 elements, the remaining array length is 1.

So the implementation of subtraction looks like this:

type Subtract<Num1 extends number, Num2 extends number> = 
    BuildArray<Num1> extends [...arr1: BuildArray<Num2>, ...arr2: infer Rest]
        ? Rest['length']
        : never;
Copy the code

The type parameters Num1 and Num2 are the minuend and the subtraction respectively, and the extends constraint is number.

Construct a num1-length array, extract num2-length elements by pattern matching, and put the Rest into the local variable Rest declared by Infer.

Return the length of Rest, which is the result of subtraction.

Have a try

In this way, we achieve subtraction by extracting the array type.

Multiply

We convert addition to array construction and subtraction to array extraction. So how do we do multiplication?

To explain multiplication, I went to elementary school textbooks and found this picture:

1 times 5 is the same thing as 1 + 1 + 1 + 1 + 1, which means multiplication is the sum of multiple addition results.

Then we add one more parameter to pass the array of intermediate results on the basis of addition, and then take the length again to achieve multiplication:

type Mutiply<
    Num1 extends number,
    Num2 extends number,
    ResultArr extends unknown[] = []
> = Num2 extends 0 ? ResultArr['length']
        : Mutiply<Num1, Subtract<Num2, 1>, [...BuildArray<Num1>, ...ResultArr]>;
Copy the code

The type parameters Num1 and Num2 are the addend and addend respectively.

Since multiplication is the sum of multiple addition results, we add a type parameter ResultArr to hold the intermediate results. The default value is [], which is equivalent to adding from 0.

Each time Num2 is added, subtract Num2 by one until Num2 is zero.

The addition process is to put Num1 elements into the ResultArr array.

So you’re recursively adding up, so you’re recursively putting elements into your ResultArr.

And then you take the length of your ResultArr and that’s the result of the multiplication.

Have a try

And just like that, we multiply by adding up recursively.

Divide

Multiplication is the sum of recursions, so isn’t subtraction the sum of recursions?

I went back to my elementary school textbook and found this picture:

We have 9 apples, and we give 3 apples to Beautiful Sheep, 3 apples to lazy sheep, 3 apples to boiling sheep, and then we have 0 apples left. So 9/3 is equal to 3.

So, the way to do division is to keep subtracting the minuend and subtracting the minuend until you get to zero, and that’s how many times you subtract.

It goes something like this:

type Divide<
    Num1 extends number,
    Num2 extends number,
    CountArr extends unknown[] = []
> = Num1 extends 0 ? CountArr['length']
        : Divide<Subtract<Num1, Num2>, Num2, [unknown, ...CountArr]>;
Copy the code

The type parameters Num1 and Num2 are the minuend and subtraction, respectively.

The type argument CountArr is used to record the number of subtractions in the cumulative array.

If Num1 is reduced to 0, then the number of subtractions is the result of division, which is CountArr[‘length’].

Otherwise, continue the recursive subtraction, with Num1 minus Num2 and CountArr adding one more element to represent another subtraction.

This implements division:

Have a try

In this way, we achieved division by recursively multiplying and recording the subtraction several times.

With addition, subtraction, multiplication and division done, let’s do some other numerical gymnastics.

Array length implementation counts

StrLen

Array length can be obtained by length, but string type cannot be obtained by length, so let’s implement an advanced type that evaluates the length of a string.

The length of the string is uncertain, so recursion is obvious. Take one at a time and count until you’re done, which is the length of the string.

type StrLen<
    Str extends string,
    CountArr extends unknown[] = []
> = Str extends `The ${string}${infer Rest}` 
    ? StrLen<Rest, [...CountArr, unknown]> 
    : CountArr['length']
Copy the code

The type argument Str is the string to be processed. The type argument CountArr is an array of counts, starting at 0 by default [].

Each time pattern matching is used to extract the remaining string after removing one character, adding one more element to the count array. Recursively takes characters and counts.

If the pattern match is not met, the count ends and the length of the count array CountArr[‘length’] is returned.

To find the length of the string:

Have a try

GreaterThan

Now that I can count, I can compare two numbers.

We keep adding elements to an array type and taking the length, B is larger if A is reached first, otherwise A is larger:

type GreaterThan<
    Num1 extends number,
    Num2 extends number,
    CountArr extends unknown[] = []
> = Num1 extends Num2 
    ? false
    : CountArr['length'] extends Num2
        ? true
        : CountArr['length'] extends Num1
            ? false
            : GreaterThan<Num1, Num2, [...CountArr, unknown]>;
Copy the code

The type parameters Num1 and Num2 are the two numbers to be compared.

The type argument CountArr is used for counting and is accumulated over time. The default value is [] to start at 0.

If Num1 extends Num2 is equal, return false.

If Num2 is reached first, then Num1 is greater. Return true.

Otherwise, if Num1 is reached first, Num2 is greater and false is returned.

If you don’t get any, you put an element in CountArr and keep recursing.

This enables numerical comparisons.

When 3 and 4 are compared:

When 6 and 4 are compared:

Have a try

Fibonacci

When it comes to numerical operations, we have to mention the calculation of the classical Fibonacci sequence.

The Fibonacci sequence is 1, 1, 2, 3, 5, 8, 13, 21, 34… Such a sequence has the rule that the current number is the sum of the previous two numbers.

F (0) = 1, F (1) = 1, F (n) = F (n – 1) + F (n – 2) (n 2 or more, n ∈ n *)

This is recursive addition, which is implemented in TypeScript type programming by constructing arrays:

type FibonacciLoop<
    PrevArr extends unknown[], 
    CurrentArr extends unknown[], 
    IndexArr extends unknown[] = [], 
    Num extends number = 1
> = IndexArr['length'] extends Num
    ? CurrentArr['length']
    : FibonacciLoop<CurrentArr, [...PrevArr, ...CurrentArr], [...IndexArr, unknown], Num> 

type Fibonacci<Num extends number> = FibonacciLoop<[1], [], [], Num>;
Copy the code

The PrevArr type parameter is an array representing the previous cumulative value. An array representing the current value when the type parameter CurrentArr is used.

The type parameter IndexArr is used to record indexes, incrementing each recursion by one. The default value is [], indicating that the index starts at 0.

The type parameter Num represents the number of digits in the array.

CurrentArr[‘length’] Returns CurrentArr[‘length’].

Otherwise, find the value of the current index, using the previous number plus the current number […PrevArr,… CurrentArr].

Then continue the recursion, index + 1, which is […IndexArr, unknown].

This is how you recursively calculate the number of a Fibinacci sequence.

We can correctly calculate that the eighth number is 21:

Have a try

conclusion

The TypeScript type system doesn’t have addition, subtraction, multiplication and division operators, so we do numeric operations by constructing and extracting array types and then taking lengths.

We did addition, subtraction, multiplication and division by constructing and extracting array types, as well as various counting logic.

Counting array lengths is one of the most troublesome aspects of TypeScript’s typing gymnastics, and one of the most confusing for beginners.

The combination of cases in this paper