Skip to content

Type Gymnastic - Part 3

Posted on:September 9, 2023 at 09:00 PM

This is part 3 of the series “Type Gymnastic”. If you haven’t already, go checkout part 2. Let’s continue with more challenges today!

Table of contents

Open Table of contents

Intro

In this series of type challenges we do a walk through of all challenges that I used in my advent calender. We will continue from where we left off - challenge 6.

Back to the basics

The last part of this series was a bit tough to get through, let’s go back to the basics with some other challenges.

Challenge 6

You are given the type

type If<B, T, F> = any;

and you want to construct a type which takes a condition (either true or false), a return type if condition is truthy, and another return type if condition is falsy. An example would be:

type Example = If<true, "a", "b">; // Example = "a"
type Example = If<false, "a", "b">; // Example = "b"

We have three generics B, T and F where B should be either true or false. In other words, we should constrain the generic B to only be a boolean:

type If<B extends boolean, T, F> = any;

The other two generics T and F are the different return types and can be anything so we don’t have to add any constraint to them.

Lastly, we want to add the logic that if B is true return T else return F. In Typescript this is a conditional type and we can add this logic by looking at if B extends true:

type If<B extends boolean, T, F> = B extends true ? T : F;

Which is the solution to this challenge!

Challenge 7

You are given the type

type OR<A, B> = any;

and you want to construct a type which accepts two generics, A and B, with the allowed values of 0 and 1, and returns the value A OR B. If you forgot your truth tables, here is a table for the logical operator OR:

Input AInput BOutput X
000
011
101
111

First we want to add some constraints to the two generics. We only want to allow 0s and 1s to be passed to the two them. To express that in Typescript we can make them extend a union of 0 and 1:

type OR<A extends 0 | 1, B extends 0 | 1> = any;

Next we need to add the logic seen in the truth table above. Basically, if any of the two generics contain a 1 we want to return 1, else we want to return 0. We will use the ternary operator to express that:

type OR<A extends 0 | 1, B extends 0 | 1> = A extends 1
  ? 1
  : B extends 1
    ? 1
    : 0;

Which is the solution for this challenge!

Challenge 8

You are given the type

type ReadonlyChristmasRecords<T> = any;

and you want to construct a type which takes all properties of a generic type and makes them readonly. An example would be:

type Prop = {
  a: string;
  b: number;
};

type Example = ReadonlyChristmasRecords<Prop>;
// Example = {readonly a: string; readonly b: number}

If you have read the previous posts in this series you will recognize some of the techniques used here. We want to use mapped types to iterate over all properties of the generic, add a readonly modifier to the properties and use indexed access types to retrieve the original type of the properties. Let’s first use mapped types to iterate over all the properties:

type ReadonlyChristmasRecords<T> = {
  [P in keyof T]: any;
};

Notice the keyof type operator. This will extract all keys of T which we will iterate over.

Next we will extract the original types using indexed access types:

type ReadonlyChristmasRecords<T> = {
  [P in keyof T]: T[P];
};

Now we are mapping over all properties of the generic and setting it equal to its original types - so we are just “cloning” T. The last step is to set the required modifier for every property:

type ReadonlyChristmasRecords<T> = {
  readonly [P in keyof T]: T[P];
};

Which is the solution for this challenge! Part 4 coming soon. Part 4

Relevant reading