TypeScript не распознает правильную перегрузку функции

Учитывая следующую реализацию функции:

function fmap<T, U>(value: T, f: (x: T) => U): U;
function fmap<T, U>(value: T | undefined, f: (x: T) => U): U | undefined;
function fmap<T, U>(value: T | null, f: (x: T) => U): U | null;
function fmap<T, U>(value: T | null | undefined, f: (x: T) => U): U | null | undefined {
    return value === undefined ? undefined : value === null ? null : f(value);
}

Я получаю правильные типы в следующих случаях:

const test1 = (v: string) => fmap(v, x => x); // ✅ expected: string
const test2 = (v: string | null) => fmap(v, x => x); // ✅ expected: string | null
const test3 = (v: string | undefined) => fmap(v, x => x); // ✅ expected: string | undefined
const test4 = (v: string | null | undefined) => fmap(v, x => x); // ✅ expected: string | null | undefined
const test5 = (v: string) => fmap(v, x => 1); // ✅ expected: number

Но не в этих случаях:

// ❌ expected: number | null (actual: number)
const test6 = (v: string | null) => fmap(v, x => 1);

// ❌ expected: number | undefined (actual: number)
const test7 = (v: string | undefined) => fmap(v, x => 1);

// ❌ expected: number | null | undefined (actual: number)
const test8 = (v: string | null | undefined) => fmap(v, x => 1);

См. Детская площадка

Удалил свой ответ. Для меня очевидно, почему первый вариант соответствует, и я думаю, что решением может быть использование NonNullable, но я не вижу этого сразу, и у меня нет времени на дальнейшее расследование.

Silvermind 15.08.2024 10:44

Я не понимаю, общий тип U выводится из возвращаемого типа f, поскольку f возвращает число в test6, U выводится как число, это ожидаемое значение, а не number | null.

HairyHandKerchief23 15.08.2024 12:16

@HairyHandKerchief23 Согласно реализации, f вызывается только в том случае, если значение не допускает значения NULL. Другими словами, test6 вернет ноль, если v не определен. Следовательно, ваше предположение неверно.

Eliya Cohen 15.08.2024 12:19

Я имею в виду, что T выводится как string | undefined вместо string, поэтому всегда используется первая перегрузка.

HairyHandKerchief23 15.08.2024 12:21

Я нашел способ сделать это без перегрузок: проверьте это

HairyHandKerchief23 15.08.2024 12:29

Впечатляющий! опубликуйте как ответ, чтобы я мог отметить его как правильный ответ.

Eliya Cohen 15.08.2024 12:31

@Silvermind, ты тоже был очень близок, тебе просто нужно было добавить дополнительную перегрузку: tsplay.dev/NDjr8W, также восстанови свой ответ, чтобы получить положительный голос.

Eliya Cohen 15.08.2024 12:33
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
5
7
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вам нужно будет извлечь типы, допускающие значение NULL, из универсального типа T.

type ExtractedNullable<T> = Extract<T, null | undefined>

function fmap<T, U>(value: T, f: (x: T) => U): U | ExtractedNullable<T> {
    return value === undefined ? undefined as ExtractedNullable<T> : value === null ? null as ExtractedNullable<T> : f(value);
}

const test1 = (v: string) => fmap(v, x => x); // ✅ expected: string
const test2 = (v: string | null) => fmap(v, x => x); // ✅ expected: string | null
const test3 = (v: string | undefined) => fmap(v, x => x); // ✅ expected: string | undefined
const test4 = (v: string | null | undefined) => fmap(v, x => x); // ✅ expected: string | null | undefined
const test5 = (v: string) => fmap(v, x => 1); // ✅ expected: number

const test6 = (v: string | null) => fmap(v, x => 1); // number | null

const test7 = (v: string | undefined) => fmap(v, x => 1); // number | undefined

const test8 = (v: string | null | undefined) => fmap(v, x => 1); // number | null | undefined 
const test1 = (v: string) => fmap(v, x => x); // ✅ expected: string
const test2 = (v: string | null) => fmap(v, x => x); // ✅ expected: string | null
const test3 = (v: string | undefined) => fmap(v, x => x); // ✅ expected: string | undefined
const test4 = (v: string | null | undefined) => fmap(v, x => x); // ✅ expected: string | null | undefined
const test5 = (v: string) => fmap(v, x => 1); // ✅ expected: number

// Should now correctly infer
const test6 = (v: string | null) => fmap(v, x => 1); // ✅ expected: number | null
const test7 = (v: string | undefined) => fmap(v, x => 1); // ✅ expected: number | undefined
const test8 = (v: string | null | undefined) => fmap(v, x => 1); // ✅ expected: number | null | undefined

где реализация?

Eliya Cohen 15.08.2024 12:55

функция fmap<T, U>(значение: T, f: (x: T) => U): U; функция fmap<T, U>(значение: T | неопределенное, f: (x: T) => U): U | неопределенный; функция fmap<T, U>(значение: T | null, f: (x: T) => U): U | нулевой; функция fmap<T, U>(значение: T | ноль | неопределенное, f: (x: T) => U): U | ноль | неопределенное {если (значение === неопределенное) {возвращение неопределенное; } Еще если (значение === null) { return null; } Еще {возврат f(значение); } }

Jolly Sim 15.08.2024 13:16

@JollySim Вам следует отредактировать свой ответ, а не добавлять его в качестве комментария.

Silvermind 15.08.2024 13:23

Другие вопросы по теме