Как стать автором
Обновить

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

  • он очень кроссплатформенный (это же отметает C#).

Прошло уже 10 лет, а разработчики тайпскрипта до сих пор не знают, что кроссплатформенности у C# много больше, чем у Go. Да и с производительностью и многопоточностью получше.

разработчики тайпскрипта - это разработчики Майкрософт со всеми вытекающими

Но ведь C# тоже разработан Майкрософт

Причем тем же самым человеком, что и TypeScript

От этого в тройне странней выбор в пользу Go. Какое-то само втыкание ножа себе же в спину.

Из ветки ответ соавтора, гугл-переводчик.

>>> https://github.com/microsoft/typescript-go/discussions/411#discussioncomment-12466988

Наше решение портировать на Go подчеркивает нашу приверженность прагматичным инженерным решениям. Мы сосредоточились на достижении наилучшего возможного результата независимо от используемого языка. В Microsoft мы используем несколько языков программирования, включая C#, Go, Java, Rust, C++, TypeScript и другие, каждый из которых был тщательно выбран на основе технической пригодности и производительности команды. Фактически, C# по-прежнему остается самым популярным языком внутри компании, безусловно.

Переход компилятора TypeScript на Go был обусловлен определенными техническими требованиями, такими как необходимость структурной совместимости с существующей кодовой базой на основе JavaScript, простота управления памятью и способность эффективно обрабатывать сложную обработку графов. После оценки многочисленных языков и создания нескольких прототипов — в том числе на C# — Go оказался оптимальным выбором, обеспечивая отличную эргономику для обхода дерева, простоту выделения памяти и структуру кода, которая точно отражает существующий компилятор, что обеспечивает более простое обслуживание и совместимость.

На зелёном поле это был бы совсем другой разговор. Но это было не зелёное поле — это порт существующей кодовой базы с 100 человеко-лет инвестиций. Да, мы могли бы перепроектировать компилятор на C# с нуля, и это бы сработало. Фактически, собственный компилятор C#, Roslyn, написан на C# и сам себя загружает. Но это не было перепроектированием компилятора, и переход с TypeScript на Go был гораздо более автоматизированным и более однозначным в своём отображении. Наша существующая кодовая база — это все функции и структуры данных — без классов. Идиоматический Go выглядел так же, как наша существующая кодовая база, поэтому порт был значительно упрощен.

Хотя это решение хорошо подходило для конкретной ситуации TypeScript, оно не умаляет наших глубоких и постоянных инвестиций в C# и .NET. Большинство сервисов и продуктов Microsoft в значительной степени зависят от C# и .NET из-за их непревзойденной производительности, надежной экосистемы и высокой масштабируемости. C# отлично подходит для сценариев, требующих быстрой, поддерживаемой и масштабируемой разработки, поддерживая критически важные системы и многочисленные внутренние и внешние решения Microsoft. Современный, кроссплатформенный .NET также обеспечивает выдающуюся производительность, что делает его идеальным для создания облачных сервисов, которые без проблем работают в любой операционной системе и у нескольких поставщиков облачных услуг. Недавние улучшения производительности в .NET 9 еще раз демонстрируют наши постоянные инвестиции в эту мощную экосистему ( https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/ ).

Давайте будем реалистами. Использование Microsoft Go для написания компилятора для TypeScript было бы невозможно или немыслимо в прошлые годы. Однако за последние несколько десятилетий мы увидели сильную и постоянную приверженность Microsoft программному обеспечению с открытым исходным кодом, отдавая приоритет производительности разработчиков и сотрудничеству с сообществом. Наша цель — предоставить разработчикам лучшие доступные инструменты, не обремененные внутренней политикой или узкими ограничениями. Эта свобода выбора правильного инструмента для каждой конкретной работы в конечном итоге приносит пользу всему сообществу разработчиков, стимулируя инновации, эффективность и улучшенные результаты. И вы не можете спорить с десятикратным результатом!

Ни один язык не идеален для всех задач, и в Microsoft мы празднуем силу, которая исходит от разнообразия языков программирования. Наша приверженность C# и .NET остается сильнее, чем когда-либо, мы постоянно совершенствуем эти технологии, чтобы предоставить разработчикам инструменты, необходимые для успеха сейчас и в будущем.

Почитав тред становится понятно, что это полнейший эпик фейл со стороны Хейлсберга.
В частности, в видео он однозначно дает понять, что он не знает, что C# уже 10 лет имеет официальную поддержку MacOS и Linux.
https://youtu.be/10qowKUW82U?t=1154
Аргументы про функциональный стиль и портирование так же не выдерживают никакой критики.

Почитав тред становится понятно

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

именно. поэтому они прекрасно знают, что у с# с производительностью и многопоточностью

И что же, просветите нас?)

они прекрасно знают

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

Вы видимо хотели написать что, C# тоже разработан Андерсом Хейлсбергом?)

А разве C# может скомпилиться в независимый статический бинарник, способный запуститься на голом ядре?

Да, в C# уже давно есть Native AOT, который именно это и делает. И это удивительно, что выбор пал именно на Go, учитывая что Андерс Хейлсберг автор C#, и в самой Microsoft экспертиза в C# должна быть явно лучше.
Я вначале подумал, что возможно выбор обоснован экосистемой, ведь бинарник придется тащить в node_modules, и хотелось бы, чтобы он был поменьше. Для эксперимента скомпилировал проекты на Go и C# с Native AOT, и, что удивительно, у C# размер бинарника меньше.

Эксперимент это не большой продукт.

NativeAOT не поддерживается всеми библиотеками.

Есть много нюансов в использовании.

С ним нельзя просто писать код используя любые возможности .NET.

Для компилятора это не важно, тут не нужно подключать 100500 библиотек и задействовать все возможности .NET.

И это удивительно, что выбор пал именно на Go, учитывая что Андерс Хейлсберг автор C#, и в самой Microsoft экспертиза в C# должна быть явно лучше.

так удивительно или все же доверимся экспертизе?

Ответ автора на youtube: https://www.youtube.com/watch?v=10qowKUW82U&t=1154s

But I will say that I think Go definitely is much more low-level. I'd say it's the lowest level language we can get to and still have automatic garbage collection. It's the most native-first language we can get to and still have automatic GC. In contrast, C# is sort of bytecode-first, if you will. There are some ahead-of-time compilation options available, but they're not on all platforms and don't really have a decade or more of hardening. They weren't engineered that way to begin with. I think Go also has a little more expressiveness when it comes to data structure layout, inline structs, and so forth.

Также из одного коммента на реддите :)

Well, I haven't designed like, 5 programming languages and written probably twice that many commercially successful compilers for the last 4 decades, so I think I'll have to defer to the guy who did when he says why this decision was made.

Пруфы?

что кроссплатформенности у C# много больше, чем у Go

Давно C# на riscv64 запускается? Я из-за этого CI/CD под riscv64 не мог сделать на гитхабе, т.к. их раннер написан на "много больше" кроссплатформенном C#.

с производительностью

Всегда был примерный паритет. Go компилируется в нативный бинарник, есть возможность писать очень оптимизированный код, при необходимости спускаясь до ассемблера. Лично добивался скорости, сопоставимой с C и Rust, когда было нужно.

многопоточностью

У Go M:N вытесняющая многопоточность, без необходимости обмазывать весь код async/await, без проблемы "Какого цвета ваша функция?".

Давно C# на riscv64 запускается? Я из-за этого CI/CD под riscv64 не мог сделать на гитхабе, т.к. их раннер написан на "много больше" кроссплатформенном C#.

Пробовали первую ссылка в гугле, ведущую на хабр https://habr.com/ru/companies/timeweb/articles/817163 ?)

Полную официальную поддержку от недоделанной частной инициативы отличить сумеете?)

Первоначальная работа по портированию платформы .NET для RISC-V архитектуры началась в 2023 году инженером Dong-Heon Jung из Samsung. Тогда платформа .NET максимум, что могла выполнить, это вывести «helloworld». Сейчас CoreCLR позволяет выполнять многие вещи, но пока все работает достаточно медленно. В текущие планы портирования не входит JIT оптимизация, функций SIMD, главное это выполнение любого кода. Основная ветка обсуждения процесса портирования RISC-V support #36748.

Там в планах даже поддержки GC (???) нет, как и много чего.

В Go поддержка riscv64 доступна еще с 2020 года.
Ну и я как раз в 2023 году и пытался найти способ запускать дотнет под riscv64, тогда даже этого не было.

Рекомендую поинтересоваться чем занимался Андерс до разработки ts.

Лучше посмотрите ролик. Он вроде там упоминает, что у .Net есть какие-то проблемы с GIT-компилятором на некоторых платформах. Ну или вроде того. Пишу по памяти. Ну и учтите что это вам говорит не абы кто, а... внезапно, lead architect of C#. Из Microsoft

Зачем же так категорично. У NodeJS есть своя ниша - I/O bound задачи, и в них он себя показывает неплохо

Ну, в задачах вроде "Берём json из A, преобразовываем и отправляем в B", инструмент достаточно комфортный.

Не совсем. Скорее NodeJS хорош в задачах вида "Ждем json из A, преобразовываем и отправляем в B"

челлендж многопотока в уходе от абьюза детерминированности синглтреда

утащил себе в цитаты

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

предсказуемость исполнений разбивается об хаос конкурентных потоков

Не в акторной модели.

именно на него лучше всего ложится текущий код TS, который содержит огромное количество рекурсивных данных (это же отметает Rust)

https://swc.rs/ - самый быстрый компилятор транспилер TypeScript, написан на Rust. Но таки да, он не делает проверку типов.

А при чем тут вообще нода?

To meet those goals, we’ve begun work on a native port of the TypeScript compiler and tools. The native implementation will drastically improve editor startup, reduce most build times by 10x, and substantially reduce memory usage

Чуваки компилятор тайпскрипта переписали, на runtime это вообще никак не повлияет.

на runtime это вообще никак не повлияет

Ещё бы. TypeScript-а не существует в Runtime.

В ноду кое-чего добавляют потихоньку. Уже можно исполнять TS, но пока там методом выпиливания типов в процессе исполнения. Со временем добавят ещё.

То о чём вы говорите это всё равно JS. У нас нет TS RunTime-а. И не уверен что хоть кто-то планирует его реализовывать.

Автор ноды Даль вроде собирался в Deno — но я не следил, как оно там.

Дык в ноде нет своего JS Runtime-а. Там V8

Ааа, sorry. Я неправильно понял сообщение. Отсутствие глагола смутило.

It's built on V8, Rust, and Tokio.

Да, я уже глянул. Но я вроде точно помню, что планы такие были.

Ну, Bun, например, нативно поддерживает TypeScript. И это вполне хорошая альтернатива NodeJS, если судить по моему личному опыту использования. Как минимум, даже код для NodeJS в Bun работает.

At its core is the Bun runtime, a fast JavaScript runtime designed as a drop-in replacement for Node.js. It's written in Zig and powered by JavaScriptCore under the hood, dramatically reducing startup times and memory usage.

Нет, не нативно.

Bun can directly execute .ts and .tsx files just like vanilla JavaScript, with no extra configuration. If you import a .ts or .tsx file (or an npm module that exports these files), Bun internally transpiles it into JavaScript then executes the file.

— https://bun.sh/docs/runtime/typescript

Нет, не нативно.

Тут спорный вопрос. С одной стороны, вы правы. С другой стороны, Bun не требует устанавливать компилятор typescript, и `bun run test.ts` не создаёт рядом `test.js`, который уже и запускает.

Просто, если уж в таком ключе про нативность говорить, то NodeJS нативно не поддерживает JavaScript, так как использует V8 для запуска JS. В свою очередь, V8 нативно не поддерживает JS, так как оно сначала перегоняет JS в байт-код, а потом уже байт-код и исполняет.

bun run test.ts не создаёт рядом test.js, который уже и запускает

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

Да, можно попросить создать, но это отдельная команда:

bun build test.ts --outfile test.js
bun build test.ts --outfile test.js --target browser
bun build test.ts --outfile test.js --target node

В цитате выше это черным по белому написано

Вообще, если вы доки дальше почитаете, то там есть такое:

Bun internally transpiles every file it executes (both .js and .ts)

То есть, он JS нативно тоже не поддерживает, получается. Я считаю, это просто неудачные формулировки в документации.

Нет, там внутри JavaScriptCore. И чтобы в нём запустить JS оно просто режет незнакомый синтаксис из TS.

В целом весь этот разговор бесмысленнен. Просто уже хотя бы потому, что для того чтобы иметь именно Typescript runtime (а не JS runtime игнорирующий TS-часть синтаксиса), надо чтобы в спеке языка это как-то подразумевалось. Т.к. язык представляет из себя всё тот же JS + типы, то нужна runtime проверка типов. А этого нет даже в спеке языка. Не совсем понятно куда её там даже цеплять, учитывая что типизация структурная.

Т.к. язык представляет из себя всё тот же JS + типы, то нужна runtime проверка типов.

Простите, а знаете ли вы, что в хаскеле — complete type erasure? Что означает в переводе на посконный: никаких типов в рантайме.

А как это относится к нашей теме? TS без типов это JS.

Типов достаточно в compile time (при соблюдении некоторых дополнительных условий, не все из которых в TS соблюдены, но даже так). В рантайме они не нужны. Ни алгебраические в хаскеле, ни set-theoretic в эликсире, ни спеки в эрланге, никакие.

Рефлексия всё сломает, конечно, и аспекты сломают, но когда я последний раз читал спецификацию TS там ни того, ни другого не было.

Иными словами, runtime проверка типов никому нахрен не нужна.

Мне нужна проверка типов в runtime. Очень. Но я видимо тот самый "никто". Но я понимаю, что её не будет, ввиду структурной типизации в TS. Её просто никак не реализовать, кроме как перебором полей, а такая проверка типов и правда не нужна.

Плюс вы затронули важную тему - типы в compile time это не только проверка. Это возможные оптимизации кода. Но ввиду тех компромиссов, которыми кишит Typescript (ввиду чего он unsound), почти все из них неприменимы.

В тоже время в языках сильно попроще (с точки зрения типов) можно всё очень сильно соптимизировать.

Мне нужна проверка типов в runtime. Очень.

А вы сами знаете ответ на вопрос «зачем»?

Это возможные оптимизации кода.

У меня нет бенчмарков, но я примерно на 102% уверен, что отсутствие оптимизации из-за плохих типов — не основная проблема с производительностью в TS.

Гораздо больше проблем принесло [справедливости ради — вызванное абсолютной необходимостью, иначе на TS никто бы никогда ничего не написал] решение разрешить тип any, который просто уничтожает все контракты и выводы типов во всех местах, куда может дотянуться.

А вы сами знаете ответ на вопрос «зачем»?

Возможно мы тут говорим о разных вещах. Я имею ввиду осведомлённость о типах данных в runtime, и чёткое понимание того с каким типом данных я имел дело здесь и сейчас в runtime. Например чтобы не прибегать к type-guard-ам (лютый костыль) или сложным ветвистым if-ам с type narrow down (работает довольно плохо). Вне структурной типизации мы просто наверняка знаем с чем мы имеем дело.

что отсутствие оптимизации из-за плохих типов — не основная проблема с производительностью в TS.

Давайте я то перефразирую. Возьмём:

struct Animal {
    AnimalKind kind;
    std::string name;
    int age;
};

Чего стоит в runtime обратиться к myAnimal.age? Да ничего: pointer to the variable + offset to the "age" field. В бинарнике уже так и зашито.

Что у нас в JS? Всё сложно, и в целом плохо. В лууучшем случае повезёт и будет hidden class. А так привет hash map и много implicit проверок.

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

Для TS... внезапно тоже. Казалось бы - все возможности, крутейшая система типов. Можно такого наворотить. Но нет. Потому, что он совсем unsound. А unsound он потому что было решено, что для убийцы Javascript нужно быть настолько гибким, что других вариантов не оставалось. Я в целом доволен. Не поймите меня не правильно.

Собственно говоря о Typescript runtime или compliator-е я имею ввиду именно подобные вещи, привычные нам по статически типизированным компилируемым языкам. Такого в TS ждать не приходится. Оттого и смысла делать отдельный TS runtime нет, ибо он ничем не будет отличаться от JS runtime, которых уже много.

---

Надеюсь теперь понятнее, что именно я имею ввиду. А то нас явно в разные стороны заносит.

Я имею ввиду осведомлённость о типах данных в runtime, и чёткое понимание того с каким типом данных я имел дело здесь и сейчас в runtime. Например чтобы не прибегать к type-guard-ам (лютый костыль) или сложным ветвистым if-ам с type narrow down (работает довольно плохо).

Есть гипотеза, что вам имеет смысл почитать про то, как работает Idris, например (или просто Haskell). Вам не нужны типы в рантайме, чтобы сделать то, что вы хотите. Вам компилятор запретит передать другой тип. Код не скомпилируется просто, если где-то вы вызовете функцию, определенную как «arity 1, arg_type: int» с параметром типа строка. А вот type-guards и условные операторы — это как раз про рантайм.

Unsoundness — это не злая фея с волшебной палочкой, которая портит вам типы. Вам разрешено портить типы (и то, не прям портить, а расширять по месту). Но если вы сами ничего не испортите, компилятор всё выведет правильно и запретит вам отстреливать себе ногу. В любом случае вы можете ничего не проверять: если скомпилировалось, то переменная ровно того типа, который написан в ближайшем объявлении.

Хрестоматийный пример:

function messUpTheArray(arr: Array<string | number>): void {
    arr.push(3);
}

const strings: Array<string> = ['foo', 'bar'];
messUpTheArray(strings);

const s: string = strings[2];
console.log(s.toLowerCase())

Принято показывать этот код, говоря про unsoundness, но это манипуляция и смещение акцентов. Смысл типов в том, что во второй строке arr.push(null) вам написать не удастся: компилятор не допустит. И тащить это в рантайм не нужно: внутрь этой функции в скомпилированном файле (ок, транспилированном джаваскрипте) — засунуть в arr что-то не то не удастся.

Чего стоит в runtime обратиться к myAnimal.age?

Я понимаю, в чем проблема :)

Мне просто кажется, что конкретно эта проблема — наименьшая в контексте производительности.

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

Вы сейчас явно не о том. Тип валидный. С чего бы компилятору мне запрещать что-то.

type-guards и условные операторы — это как раз про рантайм

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

Но если вы сами ничего не испортите, компилятор всё выведет правильно и запретит вам отстреливать себе ногу

В TypeScript 1001 способ написать код без type guard-ов, без any, без type cast-ов и прочих инструментов таким образом, чтобы код был unsound. Таких компромиссов в языке много. Можно ли их избежать в рамках структурной типизации (гипотетически)? Ну Haskel вроде смог. Во всяком случае по словам моих бывших коллег, там есть подобное. Я не проверял. Но не наш случай.

Я понимаю, в чем проблема :)

В скорости? Одна математическая операция и переход по указателю vs работа со словарём. От языка со статической типизацией обычно такой тормознутости не ждут. Но у нас в реальности JS, а не TS.

Sorry, я прочитал как "не понимаю".

наименьшая в контексте производительности.

Ну если вспоминать как люди пишут на TS\JS (преимущественно ногами), то могу с вами согласиться. Но это уже разговор, про людей, а не про языки :D

По-моему почему не C#/Rust становится понятно если просто посмотреть на сами исходники получившего компилятора. Очевидно, что это не переписывание, а портирование, вероятно даже частично автоматизированное. И трансляция из TS в Го очевидно проще просто из-за топологический схожести синтаксиса. В C# пришлось бы оборачивать все в статические классы, а в Расте еще и начался бы его обычный ад с борроу чекками и лайфтаймами.

Что обидно, поскольку конкретно приведённый фрагмент прям напрашивается на переписывание на сумм-типы

Короче, чуваки, я тоже очень расстроился, что не портировали на C#, но немного поизучав вопрос понял, почему Go. Там в исходных кодах на TS дофига всяких self-referenced структур, типа:

interface TypeSymbol {
    name: string;
    flags: number;
    parent?: TypeSymbol;
}

которые в Go переходят в

type TypeSymbol struct {
    Name   string
    Flags  int
    Parent *TypeSymbol
}

И фишка в том, что в dotNet у этого нет аналога!
Можно, сделать класс, но это будет не то, потому что нам нужно последовательное хранение в памяти в массивах:

class TypeSymbol {
    public string Name;
    public int Flags;
    public TypeSymbol Parent; // такой код не скомпилируется для struct
}

Вариант делать через unsafe тоже не подходит, потому что в таком случае Parent не будет менеджериться сборщиком мусора, в отличии от TS и Go.

unsafe struct TypeSymbol
{
    // Фиксированный буфер для хранения имени (если хотим полностью unmanaged-структуру)
    public fixed char Name[128];  
    public int Flags;
    public TypeSymbol* Parent;     // нужно делать освобождение памяти самому Marshal.FreeHGlobal
}

Лучшим решением был бы ввод что-то типа NativePtr (в F# такое есть, но и там он не под GC), но я не знаю насколько это реалистично добавить в dotNet.

struct TypeSymbol {
    public string Name;
    public int Flags;
    public NativePtr<TypeSymbol > Parent; 
}

Жаль всё равно, что решили не тратить на это ресурсы, поддержка разрабам AOT не помешала бы, но технические проблемы тут действительно серьёзные, а не просто нежелание писать неидиоматический код на С#.

А в двух словах для мимокрокодилов, что имеется ввиду под портированием? Что теперь ts можно будет транслировать в go и собрать бинарник ?

Теперь ts компилируется в js компилятором, написанным на go.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации