Чем меня разочаровал Typescript и стоит ли он того?

Автор оригинала: Paul Cowan
  • Перевод


Прежде чем начать, хочу упомянуть, что я фанат TypeScript. Это мой основной язык программирования для фронтенд проектов на React и для любой бекенд работы, которую я выполняю в Node. Я полностью за Typescript, но есть моменты, которые меня беспокоят и про которые я и хотел рассказать этой статьей.

Я писал исключительно на TypeScript в течение последних трех лет для множества разных компаний, так что на мой взгляд, TypeScript как минимум что-то делает правильно или закрывает определённые потребности.

Несмотря на своё несовершенство, TypeScript вошёл в мейнстрим фронтенд разработки и занимает седьмое место в списке наиболее востребованных языках программирования по версии HackerRank developer skills report.

Любой команде разработчиков, неважно, большая она или маленькая, пишет на TypeScript или нет, ради безопасности всегда стоит:

  • Следить, чтобы хорошо написанные юнит тесты покрывали как можно больше кода в продакшене
  • Использовать парное программирование: дополнительная пара глаз поможет поймать более серьезные вещи, чем просто синтаксические ошибки
  • Качественно построить процесс code review и выявлять ошибки, которые не может найти машина
  • Использовать линтер – такой как eslint

TypeScript хоть и добавляет дополнительный уровень безопасности поверх всего этого, но по моим ощущениям, он очень сильно отстает от других языков в этом плане. Объясню почему.

TypeScript не является надежной системой типов


Я думаю, что это, возможно, главная проблема с TypeScript, но прежде позвольте мне определить, что такое надежные и ненадежные системы типов.

Надежная система типов


Надежная система типов гарантирует, что ваша программа не попадет в недопустимые состояния. Например, если статическим типом выражения является string, при его вычислении во время выполнения вы гарантированно получите только string.

В надежной системе типов вы никогда не будете находиться в ситуации, когда выражение не соответствует ожидаемому типу, будь то во время компиляции или во время выполнения.

Существуют, конечно же, различные степени надежности, а также различные интерпретации надежности. TypeScript является в некоторой степени надежным и ловит ошибки типа:

// Type 'string' is not assignable to type 'number'
const increment = (i: number): number => { return i + "1"; }

// Argument of type '"98765432"' is not assignable to parameter of type 'number'.
const countdown: number = increment("98765432");

Ненадежная система типов


Typescript абсолютно открыто сообщает, что 100% -ая надежность не является его целью. Даже «не-цель» номер 3 в списке «не-целей TypeScript» четко гласит:
Иметь надежную или «доказуемо правильную» систему типов не является нашей целью. Вместо этого мы стремимся соблюсти баланс между правильностью и производительностью.

Это означает, что нет гарантии, что переменная имеет определенный тип во время выполнения. Я могу проиллюстрировать это на следующем несколько надуманном примере:

interface A {
    x: number;
}

let a: A = {x: 3}
let b: {x: number | string} = a; 
b.x = "unsound";
let x: number = a.x; // unsound

a.x.toFixed(0); // WTF is it?

Приведенный выше код не работает, поскольку известно, что a.x — это число из интерфейса A. К сожалению, после пары финтов с переназначением оно превращается в строку и данный код компилируется, но с ошибками во время выполнения.

К сожалению, данное выражение компилируется без ошибок:

a.x.toFixed(0);

То, что надежность не является целью языка, вероятно, одна из самых крупных проблем TypeScript. Я продолжаю получать много ошибок runtime error во время выполнения, которые не ловит tsc компилятор, но которые были бы замечены компилятором, если бы в TypeScript существовала надежная система типов. TypeScript сейчас одной ногой в лагере «надежных» языков, а другой в «ненадежных». Этот подход, состоящий из полумер основан на типе any, о котором я расскажу позже.

Меня фрустрирует факт, что количество тестов, которые я пишу, нисколько не уменьшилось с переходом на TypeScript. Когда я только начинал, то ошибочно решил, что смогу сократить тягомотную рутину написания большого количества юнит тестов.

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

Я понимаю, почему TypesScript выбрал такой путь и есть мнение, что TypeScript не был бы так популярен, если бы надежность системы типов была бы 100% гарантирована. Это мнение не выдержало проверки — язык Dart стремительно набирает популярность, одновременно с повсеместным использованием Flutter. А утверждается, что надежность типов является целью Dart.

Ненадежность и различные способы, которыми TypeScript предоставляет «аварийный выход» из строгой типизации, делают его менее эффективным и, к сожалению, делают его просто «лучше, чем ничего» в данный момент. Я был бы рад, если по мере роста популярности TypeScript стало доступно больше опций компилятора, позволяющих опытным пользователям стремиться к 100% надежности.

TypeScript не гарантирует никакой проверки типов во время выполнения


Проверка типов во время выполнения не является целью TypeScript, поэтому моё пожелание, вероятно, никогда не сбудется. Проверка типов во время выполнения полезна, например, при работе с данными JSON, возвращаемыми из вызовов API. Можно было бы избавится от целой категории ошибок и множества юнит тестов, если бы мы могли контролировать эти процессы на уровне системы типов.

Так как мы не можем ничего гарантировать во время выполнения, легко может случиться такое:

const getFullName = async (): string => {
  const person: AxiosResponse = await api();
  
  //response.name.fullName may result in undefined at runtime
  return response.name.fullName
}

Есть несколько вспомогательных библиотек, таких как io-ts, что замечательно, но это может означать, что вам придется дублировать свои модели.

Страшный тип any и опция strict


Тип any означает «любой», и компилятор допускает любую операцию или присваивание переменной с таким типом.

TypeScript хорошо работает для небольших вещей, но люди склонны ставить тип any на все, что занимает больше одной минуты. Недавно я работал над Angular-проектом и видел много такого кода:

export class Person {
 public _id: any;
 public name: any;
 public icon: any;

TypeScript позволяет вам забыть о системе типов.

Вы можете сломать тип чего угодно при помощи any:

("oh my goodness" as any).ToFixed(1); // remember what I said about soundness?

Опция strict включает следующие параметры компилятора, которые делают все более надежным:

  • --strictNullChecks
  • --noImplicitAny
  • --noImplicitThis
  • --alwaysStrict

Есть также правило eslint @typescript-eslint/no-explicit-any.

Распространение any может разрушить надежность вашего кода.

Заключение


Я должен повторить, что я фанат TypeScript и использую его в своей повседневной работе, но я чувствую, что он несовершенен и шумиха вокруг него не совсем оправдана. Airbnb утверждает, что TypeScript помог предотвратить 38% ошибок. Я очень скептически отношусь к настолько точно заявленному проценту. TypeScript не улучшает и не объединяет в себе все существующие практики хорошего кода. Мне все еще приходится писать уйму тестов. Вы могли бы поспорить, что я пишу больше кода, поэтому мне и приходится писать так много тестов. Я продолжаю получать много неожиданных runtime error.

TypeScript предлагает только базовую проверку типов, а то, что надежность и проверка типов во время выполнения не входит в его цели, оставляет TypeScript в мире полумер, на полпути между лучшим миром и тем, в котором мы кодим сейчас.

TypeScript прекрасен благодаря хорошей поддержке IDE, таких как vscode, где мы получаем визуальную обратную связь в процессе печати.


Ошибка TypeScript в vscode

TypeScript также улучшает рефакторинг и ломающие код изменения (такие как изменения в сигнатурах методов) мгновенно идентифицируются при запуске компилятора TypeScript.
TypeScript предоставляет хорошую проверку типов и определенно это лучше, чем никакой проверки или простой eslint, но я чувствую, что TypeScript может быть намного лучше и необходимые опции компилятора могли бы порадовать тех, кто хочет от языка большего.



Подписывайтесь на нашего разработчика в Instagram


Хостинг-технологии
326,67
Хостинг серверов для разработки
Поделиться публикацией

Комментарии 53

    +6
    TypeScript не гарантирует никакой проверки типов во время выполнения

    Type guards таки гарантирует. Другое дело, что эта фича не полита каким-то особым сахаром — надо проверить, так возьмите и обычным кодом проверьте.

    Страшный тип any и опция strict

    На лазейки из строгих систем типов обречен (пока, по крайней мере) любой язык. Потому что вариант «сделать что-то через лазейку» всегда лучше варианта «не сделать вообще, потому что система типов недостаточно выразительна».

    Актуальная претензия — только по первому пункту. И она не из any берется, хотя конечно весь код примера — это чуть более многословный и окольный способ написать («abc» as number).toFixed(0), а потом жаловаться.
      –3
      Больше всего печалит то, что если два разных типа содержат полностью одинаковые поля, то эти типы можно присваивать друг другу, причем без всяких кастов:

      class Dog {
          name: string = ""
      }
      
      class Cat {
          name: string = ""
      }
      
      let cat = new Cat()
      let dog = new Dog()
      
      cat = dog
      


      что полностью нарушает идеологию строгой типизации.
        +5
        Все-таки в Typescript не строгая типизация, а структурная.
          0

          Одно другому не мешает.

            0
            При этом обычно разрабы приравнивают понятия номинативной и строгой типизации. Это очевидно неправильно, но с другой стороны я нигде не видел табличку с видами типизаций и широким объяснением, какая что значит.

            В тайпскрипте по идее как раз строгая типизация в компайл-тайме. Он не разрешает имплиситных кастов. Зато делает их в рантайме. И вот вопрос, как тогда называется типизация в ts?
              0

              Табличка слишком многомерная получится просто.


              Зато делает их в рантайме.

              Я не очень понимаю, что такое каст в рантайме.

                0
                const fn = (n: number) => {...}
                
                const sample = fn('3'); // вот тут тс меня отругает
                
                const sample = fn('3' as any); 
                // вот это он сожрёт, и во время выполнения скастит '3' к 3
                  0

                  А, понятно, у меня просто терминология не совсем та. Спасибо за поясняющий пример.

                    0
                    Вопрос с правильным наименованием системы типов в тс остается открытым
                    0
                    const sample = fn('3' as any); 
                    // вот это он сожрёт, и во время выполнения скастит '3' к 3

                    Ничего TS во время выполнения не скастит. Потому что что во время выполнения тайпскрипта уже нет (ts — только во время компиляции).
                    Так что если у вас fn делает что-то типа (n: number) => n.toFixed(), то в рантайме у вас бросается исключение.

                      0
                      Ну да, не ts а js.
                      если fn делает что-то типа (n: number) => 5-n — то каст будет
            +3
            что полностью нарушает идеологию строгой типизации.
            Вы накидываете чтобы почитать холивар про сильную vs слабую типизацию или холивар про номинальную vs утиную типизацию? Или вы просто накидываете, потому что вас, как и автора, разочаровал тайпскрипт?

            А по теме: добавьте в ваш пример приватные поля (одинаковые, разумеется). Теперь ваши классы нельзя присваивать друг другу.
              +2
              Про то, что использование приватных полей делает типизацию более строгой, не знал. Спасибо.
              +7
              Потому, что на основном множестве задач JS (и TS, как следствие) duck typing ломать не нужно, это привносит накладные расходы не решая никаких проблем.
              Но если вы хотите, вы всегда можете сломать утиную типизацию руками, там, где вам это надо:
              const kind = Symbol();
              
              class Dog {
                  private [kind] = 'dog';
                  name: string = ""
              }
              
              class Cat {
                  private [kind] = 'cat';
                  name: string = ""
              }

              Или то же самое выражается интерфейсами.
                +2
                Красивый ответ. Я бы добавил еще про номинативную типизацию как подход. Даже стать я на хабре есть
                0
                Это структурная типизация, она такой и должна быть. Если хочется номинативной, это не к тайпскрипту. Но лично мне структурная нравится больше
                  0

                  А как вы в структурной типизации рекурсивные типы данных задаёте?

                    0
                    А есть какие-то особенности? Можно раскрыть мысль?
                      0

                      Ну вот в номинативной системе типов можно завести тип List для списков (положим, из целых чисел, чтобы не думать о полиморфизме), который определяется как


                      data List = Nil | Cons Int List

                      Я там в Cons использую то же имя, которое определяю.


                      В классической структурной типизации у вас нет имён для типов. Как вы будете определять понятие List? Как вы опишете Cons, в частности?

                        0
                        Мои познания в синтаксисе хаскеля оставляют желать лучшего, но если я правильно понял
                        type List<T> = null | { Cons: List<T> }
                        

                        Тут структурная типизация мне не мешает.

                        В классической структурной типизации у вас нет имён для типов.

                        Есть шанс, что в typescript она неклассическая, но тут есть имена для типов. А структурной она считается, потому что при сравнении типов несовпадение имён не имеет значения
                          0

                          А, ну тогда прикольно получается, да.


                          Не уверен, правда, что могу назвать себя фанатом структурной типизации в том смысле, что вы говорите, но это уже, наверное, вопрос субъективных предпочтений.

                            0
                            Я не фанат тоже. Я много пишу на C#, и страшно бешусь, когда надо писать такой код
                            var b = new B();
                            b.a = a.a;
                            b.b = a.b;
                            b.c = a.c;
                            ...
                            


                            причём автоматизировать это можно только рефлексией без каких либо гарантий на этапе компиляции.

                            В тайпскрипте номинативное поведение всё же можно организовать, и без кучи бойлерплейта.

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

                            В идеале, хочется ЯП с номинативными и структурными структурами данных. Я вроде читал, что так сделано в OCaml, но всерьёз рассмотреть руки не доходят
                              0
                              причём автоматизировать это можно только рефлексией без каких либо гарантий на этапе компиляции.

                              А кодогенерация? типа


                              https://github.com/cezarypiatek/MappingGenerator

                                0
                                Так проблема бойлерплейта не в том, что его лень писать, а в том, что он есть в кодовой базе
                                  0

                                  То есть автоматизировать можно не только рефлексией? А если, например генерировать автоматически и не сохранять в кодовой базе (например, partial классы), которые считать некоторым промежуточным представлением?

                                    0
                                    То есть автоматизировать можно не только рефлексией?


                                    Я бы не назвал кодогенерацию (тем более такую) автоматизацией процесса. Она автоматизирует только набор кода.

                                    Вот в java например есть автоматизации разные, типа
                                    @ constructor
                                    вешаешь на класс, и у него генерится конструктор, который ты в кодовой базе не видишь. По-моему — адский костыль, но лучше чем ничего.

                                    Минусы подхода тянут на целую статью
                                0
                                причём автоматизировать это можно только рефлексией без каких либо гарантий на этапе компиляции.


                                Automapper пробовали использовать?
                                  0
                                  Нет, но теперь буду) Как я понимаю, он тоже на рефлексии работает, верно?
                  +3
                  let b: {x: number | string} = a;

                  К этому "несколько надуманному" примеру у меня есть аналогия в таком же духе: сесть задницей на клавиатуру и сокрушаться, что код не работает так, как задумала голова.

                    0

                    Тем не менее, встроенные средства typescript уже позволяют довольно много. По первому пункту можно использовать такие техники как branded types или flowering (https://spin.atomicobject.com/2018/01/15/typescript-flexible-nominal-typing/). То же самое в виде библиотеки https://github.com/gcanti/newtype-ts .

                      +3

                      На самом деле TS уже способен на многое. Проблема скорее в том, что разработчики не достаточно хорошо этим владеют.
                      Судя по разным последним активностям, есть неплохие шансы для появления в TS поддержки higher-kinded types и type classes.

                      0

                      По рантайм-проверкам огорчает, что команда TS отказывается имплементировать генерацию метаинформации для интерфейсов, чтобы можно было хотя бы своим кодом проверять входящие DTO. Есть сторонние решения разной степени костыльности, но у них всех есть недостатки.

                        +3
                        Второй и третий пункты были изначально известны, так что они не могли быть причиной «разочарования».

                        По первому пункту, если автор горазд писать подобный код, то стоило бы тогда попробовать объявлять все типы как ReadonlyDeep<сам типа> и не знать проблем (реализацию ReadonlyDeep / DeepReadoly можно легко найти в сети).

                        Валидация в рантайте не должна быть частью TS тк TS не должен влиять на перформанс в райнайме. Кому нужно делают сами или используют множество существующих для этого библиотек. На практике, валидация в рантайме нужно если в систему поступают «ненадежные / сырые» данные от внешних источников. Но в таком случае в любом, даже строгом и компилируемом языке валидация будет выполнятся кастомным (например по описанной схеме) образом тк «сырые данные» это просто набор байт.

                        PS logrocket.com известны тем что постят подобные/сомнительные материалы ради некого хайпа, чтобы привлечь внимание к своему сервису. Персонаж Eric Elliott кстати из той же серии.
                          0
                          Но в таком случае в любом, даже строгом и компилируемом языке валидация будет выполнятся кастомным (например по описанной схеме) образом тк «сырые данные» это просто набор байт.

                          Да, но язык может статически гарантировать, что эта валидация будет выполнена.

                            0
                            ReadonlyDeep<сам типа>


                            уже добавили as const, ещё проще
                              +1
                              as const
                              Это несколько другая штука, которая не может сделать тип deep-readonly, а только значение без указания типа.
                                0
                                Ну да, кстати. Она делает выводимый тип значения deep-readonly, если быть точным
                            +5
                            Чем меня разочаровал Typescript и стоит ли он того?

                            Отвечу за автора — да стоит и всем советую.
                              0

                              В чем проблема делать проверку типов в JS рантайме то? Есть и "type of" и "instance of" и "constructor"… Для этого не нужен ни TypeScript, ни какие либо дополнительные библиотеки. Набор типов в JSON ограничен и понятен, с пользовательскими типами в остальных местах тоже вроде ясно как работать… Такое чувство, что типы — вопрос исключительно религиозный, для многих. Примеры приводятся какие-то вымороченные, вы что, реально встречали такое на практике? И даже если встречали, такое дебажится на раз-два. Я считаю, что TS — это мастхэв в любом серьезном и несерьёзном проекте, но именно как инструмент статического анализа, для этого даже писать на TS не обязательно.

                                0

                                Я бы добавил в копилку проблем TS некорректное определение сигнатуры массивов в lib.d.ts:


                                const arr: Array<{foo: {bar: string}}> = [];
                                console.log(arr[43].foo.bar); // this is fine!

                                И если проблемы описанные в статье еще можно пофиксить линтерами, то тут ничего не придумать, кроме как форкнуть typescript или писать monkey patch для lib.d.ts.


                                Но самое печальное, что разработчики специально не хотят исправлять эту проблему, потому что "тогда будет не удобно работать тем, кто привык отсутствию статической типизации".


                                Обсуждение этого бага можно найти тут.

                                  0

                                  В обсуждении справедливо упомянули, что такой оператор будет только раздражать и провоцировать императивное отбрасывание undefined.

                                    0

                                    Правильно. Зачем вообще делать статическую типизацию? Она будет только раздражать и провоцировать использование any.


                                    К слову, оператор ! совсем не обязательно использовать, а в циклах for; of и функциональных методах типа map, forEach итак не будет undefined. О чем тоже вполне справедливо заметили в комментариях.

                                    0
                                    Я бы добавил в копилку проблем TS некорректное определение сигнатуры массивов в lib.d.ts:

                                    А вы знаете хоть один распространенный язык со статической типизацией, в котором эта сигнатура корректна и указанный вами пример даст ошибку?

                                      0
                                      data class Bar(val bar: String)
                                      data class Foo(val foo: Bar)
                                      
                                      fun main() {
                                          val arr = mapOf<Int, Foo>()
                                          println(arr[43].foo.bar) // Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Foo?
                                      }

                                      https://pl.kotl.in/SETSpP6Gi


                                      Хорошо, это не совсем честный пример, поскольку это словарь, а не массив. Но даже если забыть про то, что в массивы в JS сильно отличаются от массивов в Kotlin и позволяют творить такие вещи как:


                                      const arr: string[] = [];
                                      arr[42] = "foo";
                                      if (arr.length > 0) {
                                          console.log(arr[0].toUpperCase()); // Surprise!
                                      }

                                      То со словарями совершенно та же беда:


                                      const foo: { [key: number]: string } = {};
                                      // TypeScript: It's ok!
                                      // Runtime: TypeError: foo[32] is undefined
                                      console.log(foo[32].toUpperCase());

                                      Playground Link


                                      Кстати, класс Map из ES2015 имеет корректные сигнатуры, но им не удобно пользоваться, он не всегда применим и его сложнее сериализовать/десериализовать в/из JSON.

                                        0
                                        Кстати, класс Map из ES2015 имеет корректные сигнатуры

                                        Верно, так что со словарями никакой беды нет. С-но, с объектами и массивами тоже нет — то, что там неявные undefined, это как раз преимущество тс. Т.к. без этого он был бы не нужен никому.
                                        И, собственно, вам никто не мешает объявлять так: { [index: string]: number | undefined } = {};

                                          0
                                          С-но, с объектами и массивами тоже нет — то, что там неявные undefined, это как раз преимущество тс.

                                          Так какие же в этом преимущества, кроме того что в противном случае это якобы будет всех раздражать?


                                          Т.к. без этого он был бы не нужен никому.

                                          Тоже весьма спорное утверждение, Мне нужен, всем кто проголосовал и отписался в тикете, тоже нужен. Так что это уже не никому.


                                          Хорошим решением было бы добавить соответствую настройку в tsconfig.json, но это они тоже не хотят делать по идеологическим причинам.


                                          И, собственно, вам никто не мешает объявлять так: { [index: string]: number | undefined } = {};

                                          Еще как мешает, об этом тоже уже все расписано в том тикете.


                                          Во-первых, тогда у вас все объекты будут number | undefined. Например, Object.values(foo) — вернет Array<number | undefined>, хотя мы на 100% уверены что в данном случае должен быть просто Array<number>.
                                          А во-вторых, есть огромное количество встроенных типов и сторонних библиотек, которые не возможно никак поменять.


                                          Ну и как я уже написал использовать везде Map просто невозможно в силу того, что весь остальной JavaScript мир использует для этих целей именно объекты.

                                            0
                                            Так какие же в этом преимущества, кроме того что в противном случае это якобы будет всех раздражать?

                                            Преимущества в том, что в этом случае инструмент эффективно решает свою задачу.


                                            Тоже весьма спорное утверждение

                                            Утверждение подтверждено практикой. Флоу, который два года назад был фичастее тс, а также являлся "дефолтной" типизацией для реакта — умер, а тс — взлетел. Почему? Потому что флоу решал задачу плохо, а тс — хорошо.


                                            Мне нужен

                                            Пояснение для людей, притворяющихся аутистами: в подобного рода контекстах квантор всеобщности интерпретируется не как "все", а как "все, за исключением некоторой с практической точки зрения несущественной доли".


                                            Во-первых, тогда у вас все объекты будут number | undefined.

                                            Так они и должны быть все number | undefined. Это же именно то, чего требуется от "корректного типа для мапы".


                                            хотя мы на 100% уверены что в данном случае должен быть просто Arraynumber

                                            А компилятор:


                                            1. каким образом должен узнать, что вы уверены?
                                            2. каким образом должен убедиться, что вы не ошибаетесь (а вы ошибаетесь, кек)?
                                            3. а шо, таки в котлине такой проблемы нет? есть же

                                            А во-вторых, есть огромное количество встроенных типов и сторонних библиотек, которые не возможно никак поменять. Ну и как я уже написал использовать везде Map просто невозможно в силу того, что весь остальной JavaScript мир использует для этих целей именно объекты.

                                            Так вот именно по этой причине там и нету undefined в возвращаемом типе. А в Map — есть.

                                              0
                                              Преимущества в том, что в этом случае инструмент эффективно решает свою задачу.

                                              Если под эффективность понимать написание меньшего количества символов, то да. Тогда JS еще эффективнее.


                                              Утверждение подтверждено практикой. Флоу, который два года назад...

                                              Причем здесь вообще Flow? Он вообще другим путем шел, и как раз таки в Flow всегда значительно слабее была гибкость и безопасность типов. Вполне вероятно что именно по этому он и не набрал популярности.


                                              Зато Kotlin, где null-safety на отличном уровне и работает именно так, как хотели бы видеть разработчики из того тикета, живет и процветает.


                                              "все, за исключением некоторой с практической точки зрения несущественной доли".

                                              Это ваши догадки. Даже статистика голосов на github (231 за и 2 против) говорит об обратном.


                                              Так они и должны быть все number | undefined. Это же именно то, чего требуется от "корректного типа для мапы".

                                              Нет, не должны быть. Объясню на примере:


                                              const arr1: { [key: number]: string } = {};
                                              arr1[42] = undefined; // Ошибка, TS не даст это сделать
                                              arr1.map(x => x.toLowerCase()); // Соответственно тут все хорошо, т.к. x - это string
                                              
                                              const arr2: { [key: number]: string | undefined } = {};
                                              arr1[42] = undefined; //  Нет ошибки
                                              arr1.map(x => x.toLowerCase()); // А тут уже придется страдать, потому что x - это string | undefined

                                              а шо, таки в котлине такой проблемы нет? есть же

                                              Именно, что в Kotlin этой проблемы нету. Вот пример приведенного кода выше на Kotlin:


                                              fun main() {
                                                  // Альтернатива на TS: const arr1: { [key: number]: string } = {};
                                                  val arr1 = mutableMapOf<Int, String>() 
                                                  arr1[43] = null; // Ошибка. String не nullable
                                                  arr1.values.forEach {
                                                      // Котлин гарантирует, что it не null. По этому ошибки нету
                                                      println(it.toUpperCase())
                                                  }
                                              
                                                  // Альтернатива на TS: const arr1: { [key: number]: string | undefined } = {};
                                                  val arr2 = mutableMapOf<Int, String?>()
                                                  arr2[43] = null; // OK
                                                  arr2.values.forEach {
                                                      // Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Foo?
                                                      println(it.toUpperCase())
                                                  }
                                              }

                                              https://pl.kotl.in/eUFfJ_g2B

                                                0
                                                Если под эффективность понимать написание меньшего количества символов, то да.

                                                Эффективен для того, чтобы покрывать типами жс-код и снижать, таким образом, количество ошибок. Да, отсутствие soundness в ТС позволяет более эффективно снижать количество ошибок, чем это было бы при наличии soundness. Вы поняли все верно.


                                                Причем здесь вообще Flow?

                                                При том, что это прямой аналог-конкурент тс.


                                                Он вообще другим путем шел, и как раз таки в Flow всегда значительно слабее была гибкость и безопасность типов. Вполне вероятно что именно по этому он и не набрал популярности.

                                                Как раз нет, все наоборот. Вы, видимо, не в теме просто. Во флоу type safety существенно выше чем в тс (и была и есть), и ~2 года назад флоу был строго более фичаст. Например, там был аналог mapped-типов (в тс их тогда не было), были nullable-типы (в тс не было). Тайп инференс там до сих пор лучше.


                                                Нет, не должны быть. Объясню на примере:

                                                Ваш пример как раз и объясняет, почему должны, а не наоборот. Тип { a: undefined } значит и отсутствие поля а и поле а, в котором будет лежать undefined. По-этому ваша ф-я Object.values(foo) вполне может вернуть undefined в каком-то из элементов. По-этому у нее не может быть тип T[], должен быть тип именно (T | undefined)[]. Так работает javascript.


                                                Именно, что в Kotlin этой проблемы нету.
                                                // Котлин гарантирует, что it не null. По этому ошибки нету
                                                // / Only safe (?.) or non-null asserted (!!.)

                                                Так в тс все точно так же в обоих случаях. Не понял, что вы хотели показать. Object.values с корректным типом-то на котлине написать все равно нельзя.


                                                Это ваши догадки. Даже статистика голосов на github (231 за и 2 против) говорит об обратном.

                                                Ну так ваши 231 голосов — это и есть несущественная стат. погрешность.

                                                  0
                                                  Так в тс все точно так же в обоих случаях

                                                  Именно, к этому нет претензий. Этот код работает как надо и в TS и в Kotlin. По этому не нужно указывать тип как nullable (как предлагаете делать вы) чтобы получить https://habr.com/en/company/vdsina/blog/485206/?reply_to=21230884#comment_21229908.


                                                  Чтобы подытожить все выше сказанное мной: В TypeScript весьма хорошая безопасность типов и null-safety, И, на мой взгляд, весьма близка к Kotlin. Основная разница и проблема заключается в двух моментах:


                                                  1.
                                                  const foo: { [key: number]: string } = {};
                                                  console.log(foo[32].toUpperCase()); // Runtime Error

                                                  TypeScript пропустит этот код. Аналогичный код на Kotlin не скомпилируется.


                                                  2.
                                                  const arr: string[] = [];
                                                  arr[42] = "foo";
                                                  if (arr.length > 0) {
                                                      console.log(arr[0].toUpperCase()); // Runtime Error
                                                  }

                                                  TypeScript пропустит этот код. Аналогичная ситуация на Kotlin не возможна. E.g. если количество элементов больше нуля, то самый первый элемент точно не null.


                                                  Прошу прощения, но на остальную часть комментария нет желания отвечать.

                                                    0
                                                    Именно, к этому нет претензий. Этот код работает как надо и в TS и в Kotlin

                                                    А по-разному-то какой код работает? Вы назвали проблему — в тс нельзя написать Object.values с "правильным типом". Но эта же проблема есть и в котлине — там тоже такой values не пишется.


                                                    1. TypeScript пропустит этот код

                                                    Все верно. И тот факт, что тс данный код пропустит — преимущество тс, благодаря которому тс в принципе можно использовать. Если бы было иначе — тс бы просто умер, т.к. значительную часть жс кода нельзя было бы типизировать. А кому нужна система типизации, которая не позволяет типизировать код?


                                                    Аналогичная ситуация на Kotlin не возможна.

                                                    Аналогичная ситуация на котлине невозможна, потому что на котлине нельзя написать const arr = [];, а на джаваскрипте — можно. По-этому на котлине не встает вопрос — какой тип следует приписать для данной конструкции. Ведь такая конструкция просто не существует. Для аналога тут надо использовать котлиновский mapOf<Int, Foo> который полностью эквивалентен жсовскому { [index: number]: Foo | undefined } и тогда все работает совершенно одинаково

                                    0
                                    Я думаю, что это, возможно, главная проблема с TypeScript

                                    Вообще, это главное преимущество тайпскрипта. Именно благодаря отсутствию soundness он в принципе взлетел. Любая soundness система типов для js обречена заведомо. Бивариантность же, которая ниже приводится в пример, — вообще основная киллер-фича, без нее ломается абсолютно все, т.к. именно бивариантность делает все операторы дистрибутивными относительно объединений и пересечений. Именно благодаря бивариантности в тс есть вменяемые сообщения об ошибках, cond-типы и вот это вот все.

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое