На Ютубе наткнулся на видосик, где человек разбирал какую-то задачку из челленджа. Заинтересовался, что это такое за челлендж, и сам того не заметил, как включился. Интересный оказался, но сложность местами не соответствует разделу, как мне показалось. В частности, некоторые Easy совсем не изи оказались. Это если их с максимальным уровнем сложности “Extreme” сравнивать.

https://github.com/type-challenges/type-challenges

Ниже решения первого раздела задач

Warm Up

Hello World

type HelloWorld = string // expected to be a string

Easy

Pick

type MyPick<T, K extends keyof T> = { [V in K]: T[V] };

Readonly

type MyReadonly<T> = { readonly [K in keyof T]: T[K] }

Tuple to Object

type TupleToObject<T extends readonly (string | number](]> = { [K in T[number]]: K }

First of Array

type First<T extends unknown[]> = T extends [] ? never : T[0]

Length of Tuple

На этой штуке завис… Вот что значит, не читать сообщения компилятора. Думал, что он ругается, что .length нет, но нет, он ругался на точку и просил [‘length’].

type Length<T extends readonly unknown[]> = T['length']

Exclude

Тут тоже залип. Напрочь забыл как раскручиваются юнионы типов.

type MyExclude<T, U> = T extends U ? never : T;

Awaited

Тут было проще, но пришлось рекурсивно вложенные Promise раскрутить. И само решение уже не то, чтобы уж совсем “easy” получилось

type MyAwaited<T extends { then: (arg: any) => any }> = T['then'] extends (a: (value: infer R) => any) => any ? (R extends Promise<any> ? MyAwaited<R> : R) : never

If

А вот это было реально простым.

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

Concat

И это опять простое

type Concat<T extends any[], U extends any[]> = [...T, ...U]

Includes

Нифига себе изи! Справился только через рекурсивный разбор и строгое сравнение типов из-за совершенно нелогичных кейсов, вида Includes<[1], 1 | 2>, который должен отдавать false, хотя, казалось бы, что проверяем список [1] на соответствие 1 или 2… Но нет, в тестах хотят именно строгий матч и чтобы тут был false.

И сравнение типов на равенство подсмотрел на SO: проверки на взаимный extends недостаточно из-за того что true extends boolean и boolean extends true - оба true, хотя казалось бы…

type Exact<A, B> = (<T>() => T extends A ? 1 : 0) extends (<T>() => T extends B ? 1 : 0) ? true : false
type Includes<T extends Array<any>, U> = T extends [infer V, ...infer Rest] ? Exact<V, U> extends true ? true : Includes<Rest, U> : false

Push

Ну, тут тривиально:

type Push<T extends Array<unknown>, U> = [...T, U]

Unshift

И это тоже, по сути, тот же Push, только отзеркаленный

type Unshift<T extends Array<unknown>, U> = [ U, ...T]

Parameters

Написать собственный Parameters. Ну, такое я писал, пока его ещё не было в стандартной библиотеке. Но ни разу такое не изи, хотя и не сказать, чтобы очень сложно.

type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer R) => any ? R : never

Extreme

Get Readonly Keys

Проверки ради попробовал сразу extreme-уровень, первый же Get Readonly Keys. Оказалось, что пол дела уже сделано. Сравнение придумалось сразу: Exact у меня уже был (из Includes), а долго провозился со способом хранения найденных ключей.

type Exact<A, B> = (<T>() => T extends A ? 1 : 0) extends (<T>() => T extends B ? 1 : 0) ? true : false
type GetReadonlyKeys<T> = {
  [K in keyof T]-?: Exact<Pick<T, K>, Readonly<Pick<T, K>>> extends true ? K : never
}[keyof T];

По сравнению с Includes, радикального усложнения в Get Readonly Keys я не заметил. Получается, что или Easy не весь такой уж изи, по крайней мере, некоторые задачи из него. Или Extreme не очень-то и экстремальный. Я больше склоняюсь к первому варианту.