У меня есть функция say
, которая работает следующим образом:
say()
==> ""
say("hello")()
==> "hello"
say("hello")("how")("are")("you")()
==> "hello how are you"
и реализуется в TypeScript следующим образом:
function say(first?: string) {
if (first === undefined) {
return ""
}
return (second?: string) => {
if (second === undefined) {
return first
}
return say(`${first} ${second}`)
}
}
Я оставил тип возвращаемого значения отключенным, чтобы посмотреть, что выведет TypeScript, и получил следующее выражение:
function say(first?: string): "" | ((second?: string) => string | ...)
Знак ...
, похоже, указывает на тот факт, что любая попытка вывести тип является циклической, поскольку
say
возвращает либо пустую строку, либо функцию, которая возвращает строку, либо функцию, которая возвращает строку, либо функцию, которая возвращает строку или...
Однако использование ...
при рендеринге типа в TypeScript не проясняет, где находится цикличность или рекурсия, и тип кажется неточным. Возможно, это выбор команды TypeScript. Есть ли способ более точно выразить тип? Что-то в духе
функция, которая при задании необязательного строкового параметра возвращает либо пустую строку, либо тип t, причем t — это тип функции, которая при задании необязательного строкового параметра возвращает либо строку, либо t
Кажется, вы задаете больше одного вопроса. Можете ли вы отредактировать публикацию, включив в нее только один прямой вопрос?
Ответ здесь заключается в том, что, хотя TS имеет внутреннее представление анонимных рекурсивных типов, он не может точно сериализовать их как строку. По адресу ms/TS#44045 есть запрос на функцию, позволяющую TS синтезировать имена типов, но это вряд ли произойдет. Вы можете синтезировать имя самостоятельно, как показано по ссылке на эту игровую площадку. Это полностью решает вопрос? Если да, то я напишу ответ; если нет, то чего не хватает?
@jcalz Очень понятно, значит, тип действительно внутренне представим, но TS предпочитает сериализовать его с помощью ...
, чтобы указать цикличность, а не выбирать связанную переменную, чтобы точно показать, где происходит рекурсия? Ссылка на игровую площадку чрезвычайно полезна и, безусловно, достойна ответа, если вы захотите ее написать. Псевдоним типа очень понятен!
@jsejcksn Хороший отзыв по вопросу, отредактированный для пояснения основного вопроса, который не зависит от TypeScript. Ваша детская площадка очень полезна! По какой-то причине я забыл, что выражения type
сами по себе могут быть самореферентными, и зациклился на их выражении с помощью какой-то связанной переменной. Мне нужно отложить учебники по теории и написать больше TypeScript!
Это связано с тем, что механизм вывода типов TS имеет ограничения при сериализации и отображении сложных рекурсивных типов. Хотя TS может обрабатывать эти рекурсивные типы, иногда у него возникают проблемы с их полным отображением, поэтому может показаться, что тип является неполным или циклическим.
Но мы можем определить для этого рекурсивный тип, который обрабатывает возвращаемый тип say function
. Этот тип будет строкой или функцией, продолжающей цепочку.
type Say = (s?: string) => s extends undefined ? string : Say
это рекурсивный тип, и если функция получает строку, т.е. s
не является undefined
, она возвращает другую функцию типа Say
и ff s
не определена, она возвращает строку
Здесь вы можете найти интересные дискуссии по этому поводу:
https://github.com/microsoft/TypeScript/issues/3496
Это ответ на ваш вопрос?
«TS не может обрабатывать эти сложные рекурсивные типы непосредственно в своей системе типов». Тип прекрасно обрабатывается в системе типов. В настоящее время его просто невозможно сериализовать в строку для отображения. Возможно вы это имели в виду, но это кажется двусмысленным. Не могли бы вы отредактировать, чтобы уточнить? Существуют циклические типы, с которыми TS действительно не может «обработать» в своей системе типов (например, что-то вроде type X = "" | `${X}a`
), но в этом вопросе происходит не это.
Вы правы — это может привести к путанице. Я обновлю свой ответ, чтобы прояснить этот момент. Спасибо, что указали на это!
Выглядит как рекурсивный тип
type F = (first?: string) => (string | F);
. Однако я не уверен, что язык позволит вам вызывать его более одного раза, поскольку одним из возможных типов возврата является простоstring
, который не может быть вызван.