Комментарии 13
Мы имеем два примера выразительного использования
any
определили форму ограничения для типа дженерика
А чего такого выразительного здесь в any
? Сами же пишете что
если мы не знаем какой тип должен быть на месте должны использовать
unknown
Почему здесь any
стал лучше (или не лучше?) чем unknown
? Ну и в примере с дистрибутивностью тоже можно на него заменить например.
Написать декоратор для функции, который подсчитывает количество вызовов
А вот так уже лучше не делать.
Во первых, ограничив fn: T
через (...p: any) => any
, мы получаем возможность использовать его в функции как если бы он имел этот тип. То есть например можно вызывать переданную нам функцию fn
с рандомными аргументами, потому что проверки отключены. Тут потенциал для стрельбы по ногам бесконечный.
// 100% валидный код
function test<T extends (...p: any) => any>(fn: T) {
fn(1, 2, 3)
fn("hello world")
fn(null)
}
Во вторых, мы потеряли важный документирующий аспект типов. Когда человек видит что аргументы одной функции определены через аргументы другой функции например, он понимает что тут есть какая-то логическая связь. Не говоря уже о компиляторе.
Эту функцию можно типизировать без any
проще:
function decoratorCount<A extends unknown[], R>(fn: (...args: A) => R, desc: string) {
anyAgainCounts[desc] = 0
return (...args: A) => {
anyAgainCounts[desc]++
return fn(...args)
}
}
Здесь решены все перечисленные недостатки + корректно обрабатываются дженерики в fn
.
any
до сих пор может эффективно использоваться в TC проекте, но вот как тип его использование ограничено ясными продуманными ситуациями.
Приведу примеры немного более ясных и продуманных ситуаций.
Всё дело обычно во вариантности. При написании ограничений часто нужен самый общий тип, и если этот тип параметризован то в ковариантных позициях ставится unknown
(или максимально допустимый тип), а в контравариантных never
. В этих случаях можно обходиться без any
, но это может быть затруднено 1) слишком сложными ограничениями (много букв) 2) сложностью определения вариантности. Ну а если параметр инвариантный то выбора нету совсем.
Пример:
// Тип с инвариантным параметром T
type A<in out T> = (arg: T) => T
// Тип который принимает любой A и делает с ним что-то
type B<T extends A<any>> = [T]
Тут стоит иметь в виду что можно например применить B<(arg: 1) => 2>
, при том что тип (arg: 1) => 2
на самом деле невозможно получить с помощью A
и значение такого типа невозможно присвоить в A<_>
, за исключением A<any>
. Поэтому при расстановке any
в ограничения тоже следует быть осторожным.
Ещё из интересных применений можно выделить например тайпгарды. Попробуем проверить что тип является тайпгардом, для простоты ограничимся одним параметром.
Тайпгард имеет вид:
type TypeGuard<in A, out R extends A> = (a: A) => a is R
То есть мы не можем просто так обобщить его через TypeGuard<never, unknown>
, так как R
должен быть не шире A
. Приходится использовать any
.
type IsTypeGuard<T> = T extends TypeGuard<any, any> ? true : false
type t1 = IsTypeGuard<(arg: 1 | 2) => 1>
// ^? type t1 = false
type t2 = IsTypeGuard<(arg: 1 | 2) => arg is 1>
// ^? type t2 = true
Это же будет актуально для других типов с подобными ограничениями.
"А чего такого выразительного здесь в any? Сами же пишете что"
то, что я написал относится к использованию any как тип на месте. К дженерикам это не относится. Там any работает как супер-тип, поэтому любой тип может его расширять. Т.е. речь идет об отношении типов, а не типе на месте. В дженериках не нужно делать никакого приведения к типу.
А второй момент, использование any для дистрибутивности объединений - это официальный подход описанный в документации.
Основной "косяк" any при использовании в дженериках не работает. Поэтому, думаю, его так "легко" и используют в дженериках и документации.
"Во первых, ограничив fn: T через (...p: any) => any, мы получаем возможность использовать его в функции как если бы он имел этот тип. ... "
нет, не получим. Потому что "any" мы приводим к T. Поэтому, получается совершенно безопасная реализация не загроможденная всякими служебными типами, которые нам все равно не нужны. И ваши примеры будут выдавать ошибки.
"Во вторых, мы потеряли важный документирующий аспект типов. Когда человек видит что аргументы одной функции определены через аргументы другой функции например, он понимает что тут есть какая-то логическая связь. Не говоря уже о компиляторе."-
давайте посмотрим, что видит разработчик.
Я использовал распространенный шаблон для описания типа функции с любыми параметрами и любым возвращаемым значением. И этот шаблон использован на месте параметра. Семантически все верно. Любая функция может быть парметром декоратора. Поэтому все что я хотел показать отражено в типе. А связь о которой вы говорите должна быть отражена в имени декоратора.
"Эту функцию можно типизировать без any проще:"
вот как раз, что бы такой ерундой не страдать `function decoratorCount<A extends unknown[], R>(fn: (...args: A) => R, desc: string)` я и использовал распространенный тип функции в смысле описанном выше как ограничение, и T на месте параметра. Мой пример легко читается и легко понимается. А вот этот пример, на мой взгляд, очень невыразительный. Плюс, оставляет место для дискуссии, почему не использовать пример из моей статьи вместо вашего (там где нет any)? Что бы все это убрать, я и использовал any
На часть комментария ниже я ответить не смогу. Я вообще не понял, что вы хотите сказать.
Сам я выбирал примеры, которые с одной стороны распространены, а с другой используются в документации. Поэтому мои примеры легко использовать в проекте. Тот же декоратор можно использовать для каких-нибудь дебоунсов и тротлингов. Ваши примеры я раскусить не смог. Сам я такие штуки еще ни разу не видел, но с документацией ознакомился: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#optional-variance-annotations-for-type-parameters, поэтому спасибо узнал что-то новое. Но, если есть возможность я бы хотел увидеть, что-то менее синтетическое.
то, что я написал относится к использованию any как тип на месте. К дженерикам это не относится. Там any работает как супер-тип, поэтому любой тип может его расширять. Т.е. речь идет об отношении типов, а не типе на месте. В дженериках не нужно делать никакого приведения к типу.
Не понял что за "тип на месте". Вы пишете что T extends { value: any }
это пример "выразительного использования" any
. Я просто поинтересовался чем именно он выразителен в сравнении, например, с unknown
. Ни о каком приведении речи не шло.
нет, не получим. Потому что "any" мы приводим к T. Поэтому, получается совершенно безопасная реализация не загроможденная всякими служебными типами, которые нам все равно не нужны. И ваши примеры будут выдавать ошибки.
Ну вот же, вы бы проверили хоть.
А связь о которой вы говорите должна быть отражена в имени декоратора.
Ну и почему она не должна быть отражена в типе? Я тоже могу заявить что const x: number
писать излишне, можно же всё отразить в имени const number_x: any
.
Мой пример легко читается и легко понимается.
А ещё легко ломается, см. плейграунд выше. Мало того что any
внутри функции, так ещё и as
.
Плюс, оставляет место для дискуссии, почему не использовать пример из моей статьи вместо вашего (там где нет any)?
Потому что он проще и букв меньше
Потому что он, например, сохраняет дженерики в переданной функции (я писал об этом)
Ну и в целом странная аргументация. А места для дискуссии почему any
вместо нормальных типов не остаётся значит?
На часть комментария ниже я ответить не смогу. Я вообще не понял, что вы хотите сказать.
Я привёл примеры где any
действительно может быть нужен и его проблематично/невозможно заменить.
Но, если есть возможность я бы хотел увидеть, что-то менее синтетическое.
Примеры не слишком распространённые как раз потому что ситуация где действительно необходим any
не распространённая. Примеры либо синтетические, либо километровые, но в любом случае достаточно сложные. В простых случаях уровня T extends { value: any }
я не вижу смысла использовать any
вместо never
/unknown
, потому что 1) это и так ничего не стоит 2) any
всё ещё может попасть по ногам. Лучше отучаться от его использования и знать где он действительно нужен (такое бывает редко).
Не понял что за "тип на месте". Вы пишете что
T extends { value: any }
это пример "выразительного использования"any
. Я просто поинтересовался чем именно он выразителен в сравнении, например, сunknown
. Ни о каком приведении речи не шло.
extends - это выражение об отношении между сущностями. А тип на месте - это тип переменной в коде. А про выразительность я имел в виду именно саму возможность использования any в ТС проекте. Во введении я показал, что по данному вопросу могут быть проблемы.
unknown тоже можно использовать, если это не приводит усложнению типизации. В примере с декоратором будут проблемы на ровном месте.
"Ну вот же, вы бы проверили хоть. "
Тут, получается я вас не правильно понял. Я решил, что вы про результат декоратора говорите. Пример выше в принципе не имеет смысла. Потому что мы пишем декоратор, я же четко написал, внутри декоратора нам не важно какую сигнатуру имеет декорируемая функция. Да и просто по определению декоратора - это объект, который добавляет поведение декорируемому объекту, но ничего о его поведении не знает. Именно такую семантику я и реализовал. Выше я привел примеры - это дебоунс, тротлинг
"Ну и почему она не должна быть отражена в типе? Я тоже могу заявить что const x: number
писать излишне, можно же всё отразить в имени const number_x: any
. " - Так у меня все и отражено в типе. Параметр Т и он же возврващается декоратором.
Мы имеем функцию, которая принимает определенную функцию и возвращает функцию того же типа, который принимает. Я специально не стал полагаться на инферинг и вынес Т наверх. Я говорю о том, что Именно в вашем случае должна быть связь в имени, которой получается нет. Потому что если функция как то вызывает функцию - это уже не декоратор.
"А ещё легко ломается, см. плейграунд выше. Мало того что any
внутри функции, так ещё и as
. " - если вы под рефактрингом подразумеваете изменение семантики сущности, то нужно и тип менять. Иными словами сломать все можно. И ваш пример это равносильно, например, заменить number на string, что так же может все сломать.
А что не так с as?
"Потому что он проще и букв меньше" - почему? Мой пример читается слева на право. Ваш пример с одной стороны требует пробежаться глазами туда сюда. А с другой стороны использует в дженерике второй параметр, который никак не связан с выходным параметром, что само по себе является дурным тоном и так делать не рекомендуют.
"Ну и в целом странная аргументация. А места для дискуссии почему any
вместо нормальных типов не остаётся значит? " - смотря о каких "нормальных" типах идет речь и для чего они используются. Я специально выбрал пример, в котором не нужно знать ничего о параметре. Поэтому любое загромождение типами ничего не дает для реализации. Поэтому, если вы хоnите расписать все детали я ставлю вопрос, зачем? Если вы соберетесь потом, когда нибудь, если вдруг, зачем-то работать внутри с параметрами вам придется выбрать тип под новую семантику. По моему, логично.
"Примеры не слишком распространённые как раз потому что ситуация где действительно необходим any
не распространённая. " - в библиотеках, где нужно придать форму любой функции я это видел неоднократно. Дистрибутивность юнионов так же до сих пор демонстрируется с помощью any, и не просто демонстрируется, но и используется. Вот в коде на месте да, распространено не сильно. Поэтому я и привел пример, где детализацию можно опустить и это ни на что не повлияет, а вот человеку прочитать это будет проще. Да и разработка продуктивнее, т.к. не нужно сидеть и выбирать каким же типом закодировать то, чем все равно не пользуешься.
"В простых случаях уровня T extends { value: any }
я не вижу смысла использовать any
вместо never
/unknown
, потому что 1) это и так ничего не стоит 2) any
всё ещё может попасть по ногам. Лучше отучаться от его использования и знать где он действительно нужен (такое бывает редко). " - именно об этом и статья. Что нужно не отучиваться, что-то делать "потому что", а учиться понимать, что делаешь. У any есть свои особенности, которые позволяют получать выразительный код.
Пример выше в принципе не имеет смысла. Потому что мы пишем декоратор, я же четко написал, внутри декоратора нам не важно какую сигнатуру имеет декорируемая функция. Да и просто по определению декоратора - это объект, который добавляет поведение декорируемому объекту, но ничего о его поведении не знает. Именно такую семантику я и реализовал. Выше я привел примеры - это дебоунс, тротлинг
Ну я тоже такую семантику реализовал.
Я специально не стал полагаться на инферинг и вынес Т наверх. Я говорю о том, что Именно в вашем случае должна быть связь в имени, которой получается нет. Потому что если функция как то вызывает функцию - это уже не декоратор.
Я затрудняюсь понять в чём аргумент здесь и выше вообще заключается. Я вам пишу что ваши типы небезопасны и позволяют разработчику совершить ошибку (например перепутать аргументы при вызове fn
). Напоминаю, что задача типов состоит обычно как раз в предотвращении подобного. От разговоров про семантику суть то не меняется.
А что не так с as?
Всё то же самое что и с any
(позволяют разработчику совершить ошибку). Раз вы утверждаете (в статье) что "использовать any
как тип в ТС проекте нельзя", то дозволять использование as
выглядит крайне странным. В вашем же примере as
позволяет вернуть из функции любое значение (что я продемонстрировал комментом выше) и не получить ошибку.
А с другой стороны использует в дженерике второй параметр, который никак не связан с выходным параметром, что само по себе является дурным тоном и так делать не рекомендуют.
Где является? Кто не рекомендует? Совершенно безосновательные заявления, особенно когда в конкретном рассматриваемом случае это объективно работает лучше.
Я специально выбрал пример, в котором не нужно знать ничего о параметре. Поэтому любое загромождение типами ничего не дает для реализации.
Ну это не правда как минимум. Когда вам действительно не нужно ничего знать вы не пишете ограничения. Пример где не нужно знать ничего о параметре это identity function. У вас же о параметре как минимум нужно знать что 1) это функция 2) ещё можно вызвать с определённым набором аргументов, который мы получим в декораторе и передадим ей.
Если вы соберетесь потом, когда нибудь, если вдруг, зачем-то работать внутри с параметрами вам придется выбрать тип под новую семантику.
Уже работаем ведь. Когда вы пишете return (...args) => fn(...args)
чтобы обернуть декорируемую функцию вы её, внезапно, вызываете. Чтобы вызвать fn
необходимо знать какие аргументы она принимает и доказать что ...args
ей подходят. Ну или поставить any
и отключить все проверки (получив дырки, см. выше). По моему, логично.
я это видел неоднократно
Я неоднократно видел как код обмазывают any
и расставляют @ts-ignore
, это не значит что это хорошая/выразительная практика или вроде того.
Ну я тоже такую семантику реализовал.
Да. Просто в рамках поставленной задачи вы ничем этим не пользуетесь. По сути, вы реализовали подход как у меня в статье с Parameters вид сбоку. Вот прям, тоже самое, но вместо функции у вас в дженерике параметры. Да, можно и так. Просто я не очень понимаю этой мульки типизации ради типизации.
Я затрудняюсь понять в чём аргумент здесь и выше вообще заключается. Я вам пишу что ваши типы небезопасны и позволяют разработчику совершить ошибку (например перепутать аргументы при вызове
fn
). Напоминаю, что задача типов состоит обычно как раз в предотвращении подобного. От разговоров про семантику суть то не меняется.
Какую ошибку? При вызове декорированной функции у нее будет тот же тип, что и как если бы она была не декорируемая. Мой декоратор не меняет типа. Поэтому он безопасен и разработчик не сможет совершить ошибки, которую вы пишете. А вот если он захочет переписать декоратор изнутри, то да, тут нужно будет менять типизацию под новую семантику. Если и в самом деле предполагается, что функция будет переписываться изнутри постоянно, нужно подумать о другом дизайне типов. Но пример задачи был не в этом.
Всё то же самое что и с
any
(позволяют разработчику совершить ошибку). Раз вы утверждаете (в статье) что "использоватьany
как тип в ТС проекте нельзя", то дозволять использованиеas
выглядит крайне странным. В вашем же примереas
позволяет вернуть из функции любое значение (что я продемонстрировал комментом выше) и не получить ошибку.
Вы зря не говорите о каких ошибках идет речь. Если речь идет об ошибке использования декорируемой функции клиентским кодом, то таких ошибок не будет. Если речь идет о рефакторинге кода декоратора с целью изменения семантики, то тут как писал выше с типами придется по другому работать.
Где является? Кто не рекомендует? Совершенно безосновательные заявления, особенно когда в конкретном рассматриваемом случае это объективно работает лучше.
В данном конкретном примере оно вообще не работает и не нужно, потому что ваши типы, которые вы так подробно расписали вы нигде в реализации не используете. Это просто детализация ради детализации. В целом ничего плохого в этом нет, но можно и по другому.
Антишаблон, который имел в виду - это когда есть параметры в дженерике, которые можно опустить. По сути, вы получили тот же самый код, что и в статье, но вместо функции в дженерике вставили ее параметры. И так же использовали инферинг. Это именно похоже на антишаблон. Но, при втором рассмотрении я вижу, что это не он.
Ну это не правда как минимум. Когда вам действительно не нужно ничего знать вы не пишете ограничения. Пример где не нужно знать ничего о параметре это identity function. У вас же о параметре как минимум нужно знать что 1) это функция 2) ещё можно вызвать с определённым набором аргументов, который мы получим в декораторе и передадим ей.
Да. Один момент нужен. Мы его вызывать будем. Собственно этот момент я и затипизировал.
Уже работаем ведь. Когда вы пишете
return (...args) => fn(...args)
чтобы обернуть декорируемую функцию вы её, внезапно, вызываете. Чтобы вызватьfn
необходимо знать какие аргументы она принимает и доказать что...args
ей подходят. Ну или поставитьany
и отключить все проверки (получив дырки, см. выше). По моему, логично.
О каких дырках идет речь? Идет сквозная переброска параметров. Ничего знать о параметрах нам не нужно. Ничего доказывать тоже не нужно. Все что нам важно для реализации, что перед нами функция. А какая у нее сигнатура нам не интересно.
Я неоднократно видел как код обмазывают
any
и расставляют@ts-ignore
, это не значит что это хорошая/выразительная практика или вроде того.
А это здесь причем? Вот вы прямо сейчас можете посмотреть как реализованы утилита Parameters. Почему я в коде не могу делать так же в местах, которые для этого подходят?
Да. Просто в рамках поставленной задачи вы ничем этим не пользуетесь. По сути, вы реализовали подход как у меня в статье с Parameters вид сбоку. Вот прям, тоже самое, но вместо функции у вас в дженерике параметры. Да, можно и так. Просто я не очень понимаю этой мульки типизации ради типизации.
В данном конкретном примере оно вообще не работает и не нужно, потому что ваши типы, которые вы так подробно расписали вы нигде в реализации не используете. Это просто детализация ради детализации. В целом ничего плохого в этом нет, но можно и по другому.
Использую. Пользуюсь тем что не могу написать fn(1, 2, 3, null)
. Пользуюсь тем что не могу написать return (...args) => fn(...args), "nonsense"
. Это всё нужно во время разработки реализации этой функции и позже во время её возможного рефакторинга.
Какую ошибку? При вызове декорированной функции у нее будет тот же тип, что и как если бы она была не декорируемая. Мой декоратор не меняет типа. Поэтому он безопасен и разработчик не сможет совершить ошибки, которую вы пишете.
Я же привёл пример, дважды. Ошибку возможно совершить внутри функции при её реализации или рефакторинге.
А вот если он захочет переписать декоратор изнутри, то да, тут нужно будет менять типизацию под новую семантику. Если и в самом деле предполагается, что функция будет переписываться изнутри постоянно, нужно подумать о другом дизайне типов.
Если речь идет о рефакторинге кода декоратора с целью изменения семантики, то тут как писал выше с типами придется по другому работать.
Перед тем как его переписывать, его надо хотя бы написать в первый раз. И в этот первый раз тоже можно наделать ошибок. Я правда не понимаю почему вы считаете что вызывающий код может полагаться на типы, а код внутри функции не должен. Как будто там ошибиться нельзя или что. Вы пишете статью которая начинается с "использовать any
как тип в ТС проекте нельзя", затем используете any
как тип в своём же примере, а теперь говорите что "ну мне тут не важно, если надо будет рефакторить я переделаю". Сами же противоречите себе.
Я как бы не отрицаю что кому-то иногда может быть реально не важно, ну any
и any
. Те, кто так пишет, не нуждаются в советах что его нельзя использовать. Тут проблема именно в противоречии с вашей стороны.
О каких дырках идет речь? Идет сквозная переброска параметров. Ничего знать о параметрах нам не нужно. Ничего доказывать тоже не нужно. Все что нам важно для реализации, что перед нами функция. А какая у нее сигнатура нам не интересно.
Ну смотрите. Вот вам пришла любая функция, о которой вы ничего не знаете, кроме того что она является функцией. Можете ли вы её вызвать? Нет, не можете, потому что не знаете какие аргументы она принимает. Вдруг туда надо передать числа? А если строки? А если я передам null
? Если эта информация не передана её негде взять. Чтобы не выстрелить себе в ногу такие функции следует типизировать как (...args: never) => unknown
, что буквально это и значит. Функция что-то принимает, но мы понятия не имеем что и не можем её вызвать.
А что же делать когда функцию всё-таки надо вызвать? Как в случае с обёрткой. Мы не знаем какие конкретно аргументы она принимает, но можем доказать, что для любого набора аргументов A
наша обёртка будет иметь такой же набор аргументов. Это делается через (...args: A) => fn(...args)
.
В случае с (...args: any)
все проверки просто отключаются и никаких гарантий что это нужный набор аргументов уже нет.
Вот вы прямо сейчас можете посмотреть как реализованы утилита Parameters. Почему я в коде не могу делать так же в местах, которые для этого подходят?
Вы можете в принципе делать что угодно. Я уже написал почему считаю что any
использовать нежелательно без крайней необходимости - потому что нюансов много и есть шанс сделать что-то не так. Конкретно в случае с Parameters
например ничего не будет, просто обобщать и заявлять о "выразительности" я бы не стал.
Использую. Пользуюсь тем что не могу написать
fn(1, 2, 3, null)
. Пользуюсь тем что не могу написатьreturn (...args) => fn(...args), "nonsense"
. Это всё нужно во время разработки реализации этой функции и позже во время её возможного рефакторинга.
Вы же понимаете, что по условиям задачи вам это и не нужно писать? И более того, не будет нужно. Я про вызов функции.
Я же привёл пример, дважды. Ошибку возможно совершить внутри функции при её реализации или рефакторинге.
Этот пример не имеет отношения к поставленной задаче. Именно об этом я сразу в статье и указал. Вы Никогда не будете вызывать функции в декораторе. Вот если вы декорируете параметры - это другое дело. Тут нужен другой дизайн типов. Но я декорирую функцию и никак не меняю ее тип на месте. Это разные не пересекающиеся задачи.
Перед тем как его переписывать, его надо хотя бы написать в первый раз. И в этот первый раз тоже можно наделать ошибок. Я правда не понимаю почему вы считаете что вызывающий код может полагаться на типы, а код внутри функции не должен.
Потому что такого условие задачи. Мы ничего не делаем ни с функцией ни с ее параметрами.
Как будто там ошибиться нельзя или что. Вы пишете статью которая начинается с "использовать
any
как тип в ТС проекте нельзя", затем используетеany
как тип в своём же примере, а теперь говорите что "ну мне тут не важно, если надо будет рефакторить я переделаю". Сами же противоречите себе.
Прочитайте заголовок этой части статьи еще раз. Я не использую any как тип. Я использую any по прямому назначению, т.е. отключаю типизацию там, где не собираюсь ее использовать.
Ну смотрите. Вот вам пришла любая функция, о которой вы ничего не знаете, кроме того что она является функцией. Можете ли вы её вызвать? Нет, не можете, потому что не знаете какие аргументы она принимает.
Что значит вызвать? В моем примере речь идет о выполнении контракта, когда мы все входные параметры перебрасываем входной функции. Т.е. физически в коде мы всегда перебрасываем на вход декорируемой функции все, что нам приходит. Для этого нам вообще ничего ни о каких типах знать не нужно.
Вдруг туда надо передать числа? А если строки? А если я передам
null
? Если эта информация не передана её негде взять. Чтобы не выстрелить себе в ногу такие функции следует типизировать как(...args: never) => unknown
, что буквально это и значит. Функция что-то принимает, но мы понятия не имеем что и не можем её вызвать.
Какое все это имеет отношение к поставленной задаче?
А что же делать когда функцию всё-таки надо вызвать? Как в случае с обёрткой. Мы не знаем какие конкретно аргументы она принимает, но можем доказать, что для любого набора аргументов
A
наша обёртка будет иметь такой же набор аргументов. Это делается через(...args: A) => fn(...args)
. В случае с(...args: any)
все проверки просто отключаются и никаких гарантий что это нужный набор аргументов уже нет.
Как раз, что бы такой ерундой не страдать на ровном месте. Я и отключаю типизацию. То о чем вы пишете - это чистый формализм. Для сквозной передачи параметров никакой отдельной уверенности не требуется.
Вы можете в принципе делать что угодно. Я уже написал почему считаю что
any
использовать нежелательно без крайней необходимости - потому что нюансов много и есть шанс сделать что-то не так. Конкретно в случае сParameters
например ничего не будет, просто обобщать и заявлять о "выразительности" я бы не стал.
Вопрос то как раз об этом. Вот вы видите в коде тоже самое, что и в Parameters и запускается какой-то бесконечный цикл рефлексии. А вот отталкиваться от конкретной задачи разве не вариант? Зачем выдумывать примеры, которые противоречат семантике поставленной задачи? Зачем говорить о недостатке уверенности, если в коде работаем с одной единственной переменной в 2х местах? Откуда у вас такая неуверенность?
Я вполне понимаю, когда есть внутренняя культура команды. Когда уровень команды известен. Но когда идет реакция на any как на красную тряпку, по моему, это контрпродуктивно.
Вы же понимаете, что по условиям задачи вам это и не нужно писать? И более того, не будет нужно. Я про вызов функции.
Этот пример не имеет отношения к поставленной задаче. Именно об этом я сразу в статье и указал. Вы Никогда не будете вызывать функции в декораторе.
Потому что такого условие задачи. Мы ничего не делаем ни с функцией ни с ее параметрами.
Ну вот же, ваш же код из статьи:
export const anyAgainCounts: { [key: string]: number } = {}
const decoratorCount = function<T extends (...p: any) => any>(fn: T, desc: string): T {
anyAgainCounts[desc] = 0
return ((...params: any[]) => {
anyAgainCounts[desc]++
return fn(...params)
// ^^^^^^^^^^^^^ вызываем функцию!!!
}) as T
}
То есть это как минимум нужно. Помимо того что мы обезопасили себя от того, что не нужно.
Я использую any по прямому назначению, т.е. отключаю типизацию там, где не собираюсь ее использовать.
А в чём по вашему разница? Вы тут утверждаете что писать const f: (...args: any) => any
плохо, а const f: T extends (..args: any) => any
нормально? При том что они имеют буквально одни и те же недостатки?
В моем примере речь идет о выполнении контракта, когда мы все входные параметры перебрасываем входной функции. Т.е. физически в коде мы всегда перебрасываем на вход декорируемой функции все, что нам приходит. Для этого нам вообще ничего ни о каких типах знать не нужно.
Проблема в том что выполнение этого контракта никак не контролируется кроме ваших глаз. ТС нужен чтобы делать такие вещи надёжными.
Для сквозной передачи параметров никакой отдельной уверенности не требуется.
Вот эти аргументы можно хоть к чему применить. Я могу заявить что любой кусок кода мне абсолютно понятен и типы там не нужны, потому что это лишняя морока. И может даже я правда такой крутой, но зачем тогда тайпскрипт? И откуда тогда берётся "использовать any
как тип в ТС проекте нельзя"?
А вот отталкиваться от конкретной задачи разве не вариант? Зачем выдумывать примеры, которые противоречат семантике поставленной задачи? Зачем говорить о недостатке уверенности, если в коде работаем с одной единственной переменной в 2х местах? Откуда у вас такая неуверенность?
Компиляторы совершают ошибки намного реже чем люди. Эти инструменты созданы для того, чтобы на них полагаться, вот я и предпочитаю это делать, а не перекладывать их работу на себя.
Но когда идет реакция на any как на красную тряпку, по моему, это контрпродуктивно.
Я уже писал что не имею ничего против any
в целом, пусть люди пишут как им удобно. Меня конкретно во всей этой истории смущает что в вашей статье сначала рекомендуется не использовать any
для переменных и затем приводится именно такой пример его "выразительного" использования. По отдельности меня ни то, ни другое не смущает, но вместе они противоречат друг другу.
То есть это как минимум нужно. Помимо того что мы обезопасили себя от того, что не нужно.
От чего вы тут себя обезопасили? Вы же понимаете, что какие бы типы вы сюда не вставляли из моей или из вашей реализации - это ровным счетом не имеет значения, если речь идет о сквозной переброске параметров?
А в чём по вашему разница? Вы тут утверждаете что писать
const f: (...args: any) => any
плохо, аconst f: T extends (..args: any) => any
нормально? При том что они имеют буквально одни и те же недостатки?
Повторяться не буду, прочитайте еще раз о том, что означает тип на месте и значит выражение extends.
Проблема в том что выполнение этого контракта никак не контролируется кроме ваших глаз. ТС нужен чтобы делать такие вещи надёжными.
В данном случае, ТС конролирует все, что нужно. Нам нужно, что бы параметр был вызываемым и нужно пересылать туда все, что пришло со входа. Моя система типов полностью покрывает эту потребность.
И надежность не должна существовать в вакууме. А должна отвечать тем целям, которые перед собой ставишь. Что вы собрались сделать более надежным в рамках этой задачи и я пока так и не понял.
Вот эти аргументы можно хоть к чему применить. Я могу заявить что любой кусок кода мне абсолютно понятен и типы там не нужны, потому что это лишняя морока. И может даже я правда такой крутой, но зачем тогда тайпскрипт? И откуда тогда берётся "использовать
any
как тип в ТС проекте нельзя"?
Вот как раз для этого и написана статься, что бы показать не должно быть красных тряпок самих по себе. Если вы можете подобным образом объяснить все что угодно я очень удивлюсь. А просто потому, что вы даже этот небольшой случай выразительно показать не сумели. Ваши аргументы не имеют отношения к поставленной задаче.
И опять же, вот я у вас спрашиваю. Есть моя реализация через Params, есть ваша реализация через входное и выходное значение как из них выбрать то, что подходит этой задаче? А ответ такой, что это не имеет значения. Ни ваша ни моя система никак не толком не используются в реализации. И вся эта типизация нужна только для того, что бы не забыть написать "..." перед params. Вы, например, даже забыли выходное значение явно указать, что бы коллеге программисту не пришлось читать весь исходный код, что бы понять, что же там инферится. Ну, какой-то дружественный подход к разработке должен же быть, или нет?
Вы в своих примерах, и выходное значение указать забыли, и бесполезными in, out перегрузили, но зато уверены, что когда-нибудь потом кто-то нечаянно не сотрет ... перед params. Это точно разумно?
И возникает вопрос, сколько всего кода тогда в системе перегружено маркерами типа in, out которые не нужны? Сколько наоборот не помечено выходными значениями, что бы пользоваться было удобнее?
Компиляторы совершают ошибки намного реже чем люди. Эти инструменты созданы для того, чтобы на них полагаться, вот я и предпочитаю это делать, а не перекладывать их работу на себя.
Очень здравое замечание. Но код исходный вы для кого пишете? Для себя или для компилятора? А сколько времени в проекте вы потратите на анализирование исходного кода перегруженного типизацией, которая вообще ни к месту?
Я мог бы с вами согласиться, что мои примеры подходят для переходного периода с js на ts, а в ts проекте им делать нечего. Но аргументируя мои примеры вы начали:
1. Менять семантику предложенной функциональности.
2. В своих примерах использовать маркеры никаким образом не имеющие отношения к делу. В самой документации на ТС даже специально написали, что они подходят только для очень специфических случаев оптимизации работы ТС с определенными типами. А вы их просто зафигачили как будто это выразительные флажки какие-то.
Поэтому, хотите полагаться на ТС? Хорошее желание. Но качество кода определяется не только типовой системой, которая охватывает как можно больше каких-либо ошибок.
Меня конкретно во всей этой истории смущает что в вашей статье сначала рекомендуется не использовать
any
для переменных и затем приводится именно такой пример его "выразительного" использования.
Еще раз, я же ясно написал, что использую Отключение типизации. И вы сами пишете, что я использую отключение типизации. Вот прямо сейчас то зачем опять говорить, что я чему-то там противоречу? Т.е. я знаю, что я Отключаю типизацию. И при этом получаю выразительную короткую сигнатуру, которую любой человек быстро в одно направление прочитает.
Вы считаете, оно того не стоит. Вот это дискуссионный вопрос. В реальном проекте - это бы решилось путем простого голосования и последующего анализа.
Забавно, что даже для тайпгардов можно обойтись без any, если использовать бивариантную функцию:
type TypeGuard<A, R extends A> = {f(a: A): a is R}['f'];
del
@Vitaly_js Спасибо за статью, мне понравилась -- текст последователен и информативен.
Скажите пожалуйста, есть ли у вас мнение насчет каста через as или <> ? Было бы интересно почитать статью в вашем изложении по этой теме.
Снова any. Заметка для новичка