Зачем senior developer-у с его почти европейской зарплатой брать квартиру стоимостью в 150К евро в ипотеку на 30 лет, если она при таком раскладе выплачивается лет за 10 максимум (мы же не дураки, чтобы не накопить хоть какого-нибудь первоначального взноса)?
Дело даже не в налогах. Дело в том, что цены на квартиры в каком-нибудь Мюнхене начинаются от условных 500К евро, а в Питере за какие-нибудь жалкие 150К евро (в доковидные времена, сейчас всё сильно поменялось) можно купить что-то приличное в не очень многоэтажном недалеко от центра. При этом и сама ипотека быстрее платится, т.к. в целом дешевле жизнь. При этом работая из России можно получать почти европейские зарплаты. Я понимаю, что при этом в Германии несравнимо лучше городская среда, но в ней я просто не имею выбора: либо покупай квартиру по конским ценам, либо не покупай вовсе. Мне известны контраргументы: да кто же в Германии живёт в своём жилье? Ну не знаю, меня, например, очень заботит вопрос, где я буду жить после пенсии. Хотелось бы в этом вопросе чуть больше понадеяться на свои силы, а не делегировать это государству.
Всё-таки разработчики — профессия, в которой национальность, родной язык и прочие подобные вещи значения почти не имеют. Опыт, скиллы и амбиции — вот что главное.
Вот не знаю, совсем не могу согласиться! Я не работал в других отраслях и не могу про них судить - может там всё в разы хуже. И не работал прямо в других странах. А так, в уютной айтишечке если работаешь с иностранными коллегами и всего лишь переписываешься/перезваниваешься с ними, тоже чувствуются национальные особенности, менталитет. Не то, чтобы это было какой-то непреодалимой проблемой, но тем не менее дискомфорт ощущается и при прочих равных работать с русскими коллегами в разы проще.
Всё так. У меня по этому поводу как раз синдром самозванца. Мне постоянно присылают непрошенные вакансии на Java/Kotlin (и почему-то даже .NET) senior developer. Я читаю список требований и понимаю, что вообще мои текущий знания нерелевантны. И что мне делать, если меня внезапно попрут с текущего места? Остаётся только надеяться, что рано или поздно я выйду на людей, которых впечатлит мой опыт и мои скилы и которые поверят, что вспомнить Spring, которого я лет 8 уже не трогал, я быстро смогу. Но учитывая конвейер найма, не думаю, что это случится прямо совсем быстро. Останется мне с апломбом приходить и заявлять, что я синиор и продаю не знания какого-то там фреймворка, а общеинженерные навыки. Интересно, вот действительно, что важнее: понимать, как в очередном фреймворке используются корутины Kotlin или глубокое понимание устройства этих самых корутин и причин именно такого их дизайна в языке и рантайме языка?
Ещё такой момент, что в любом случае в каждой компании есть своя культура, своя специфика. В неё всё равно придётся вникать, и порой там пласты будут побольше, чем просто разобраться в каком-нибудь стеке. Так что совокупный вклад стека в онбординг будет не таким уж большим.
Конечно, это работает, если человек переходит с бэкэнда на бэкэнд - стеки хоть и разные, но общие подходы в принципе уже давно отработаны и ничего принципиально нового так же не то, чтобы очень много появляется. А вот с бэкэнда на фронтэнд перейти или вообще попасть в бэкэнд из чего-то где нет такого разделения (например, из разработки компиляторов) будет сложнее, т.к. нет понимания, какие задачи возникают и какие бывают подходы к их решению.
И главное: вот нанимаешь такой человека с хорошим резюме. И вещи он правильные на собеседовании говорит. А как нанимаешь - он сидит и пишет комментарии на Хабре ничего не делает. Так что в этом смысле конкретный стек вообще вещь не принципиальная.
Это ещё поправят, думаю, компилятор ещё неплохо так оптимизируют
Увы, но нет. Пока с самой спекой wasm не сделают чего-то, никакой оптимизатор погоды не сделает. Есть множество причин, почему код wasm работает медленно, например:
При любом доступе к куче делается bound check. Это в какой-то степени компенсируется оптимизатором, но очень часто у оптимизатора просто не хватает информации, чтобы понять, что проверку можно убрать
В Wasm нельзя получить указатели на локальные переменные. Это значит, что если вы пишете код: "int a = 0; foo(&a);", то, кроме небольшого числа тривиальных кейсов, которые может распознать оптимизатор, копилятору придётся запрятать a в shadow stack, что и само по себе не быстро, а учитывая, что shadow stack находится в куче, доступ к которой проверяется, выходит совсем уж печально
В Wasm нельзя походить по стеку. Это серьёзно ограничивает возможности по эффективной реализации GC и исключений. Что GC, что исключения, уже давно обещают, но воз и ныне там. И судя по черновикам, тот же GC (точнее, модель "кортежей") получится куцым и малопригодным для реальных нужд. С исключениями ситуация вроде получше (мне и черновик больше нравится, и шансов у него добраться до финальной спеки выше), так что возможно в каком-то обозримом будущем для кода на C++, активно использующем исключения, мы действительно увидим существенный прирост производительности.
В Wasm нельзя делать трюки с memory protection, которые так же можно использовать, чтобы генерировать segfault при разыменовании нулевого указателя или переполнении стека.
Ваша методика бенчмаркинга не выдерживает никакой критики. Движкам с JIT необходимо прогреться, собрав статистику по выполнению кода и затем оптимизировав его. Соответственно, перед замерами тестируемый код надо погонять в цикле. Далее, сам замеряемый код так же необходимо прогнать несколько раз. То же самое желательно делать и с Wasm, т.к. никто не гарантирует, что конкретная среда не делает JIT. Далее, сам код - нарочито неоптимальный. По сути вы тут тестируете как быстро среда делает вызовы функций. Было бы интереснее что-то повнушительнее: рейтрейсинг, deflate, компиляция C, физический движок. Я понимаю, что для вводной статьи это слишком страшные вещи, так что хотя бы можно было потестировать на трёх версиях fib: вашей (рекурсивная, экспоненциальная сложность), рекурсивная с мемоизацией, итеративная.
Реально же проекты, где необходима мультиплатформенность с единой кодовой базой, в природе не встречаются.
Ну почему же. Вот, например. Правда, кросплатформенность и без Kotlin бывает, на обычной Java. Для iOS был MOE, а сейчас GraalVM. На Android и на сервере нативно работает. На web — TeaVM. Правда, на сервере не надо запускать весь код клиента, достаточно только части, отвечающей за OT.
Очень интрузивное и тяжелое изменение, которое заставляет код выполняться совсем не так, как написано, разрезая сверху до самого основания ваш код на suspendable и не-suspendable. Loom гораздо более элегантен и прозрачен.
Думаю, некорректно сравнивать корутины и Loom. Корутины — это языковой механизм, который позволяет в том числе на уровне языка легко запилить аналог Loom. Но не обязательно их для этого использовать. Например, с их помощью можно делать генераторы последовательностей.
Мутабельные данные — отличный способ отстрелить себе задницу, ящитаю.
Ахаха, вы это скажите сотрудникам Kotlin team, которые ловеринги пишут не путём "я пересоздам все IR элементы от рута до текущего элемента", а путём старого доброго изменения мутабельного свойства. Думаете, они там дураки сидят, которые не знают всех преимуществ иммутабельных данных и не могут в ФП? Боюсь, если бы они делали IR иммутабельным, kotlinc работал ещё раз в 100 дольше, чем он работает сейчас.
Ну а раз в любом случае это будет POJO с final-полями, то отчего бы не обмазать его префиксом data и не получить сахарок в виде copy-метода?
Так если это иммутабельный класс, то бога ради. Я просто вижу, как на вполне мутабельных расставляют. Впрочем, в Kotlin не всё так хорошо с иммутабельностью. Вот вам маленькая задачка от меня. Что выведет следующий код?
fun main() {
val trollList = mutableListOf(1)
val a = A(foo = trollList)
val s = mutableSetOf(a)
trollList += 2
s -= a
println(s)
}
data class A(val foo: List<Int>)
Как так, мы столько сил вложили в изучение джавы, стали синьорами, а нам тут говорят, что какой-то котлин лучше, чем наша дорогая джава?
Синьоры стали синьорами не потому, что все свои 10-15 лет в индустрии потратили на изучение синтаксиса и идиом конкретно взятого языка. Они изучали такие вещи как:
Несколько других языков, хотя бы на базовом уровне. JS, чтобы что-то на фронте быстро подправить, не дожидаясь фронтэдеров (а то и вообще стали сами full stack). C++, чтобы написать performance critical код. C#, потому что в своё время успели на несколько лет переметнуться в .NET.
Обширный набор фреймворков и библиотек для языка (стандартная библиотека Java, Spring, Hibernate, Jackson, log4j и т.д.)
Базовые алгоритмы, структуры данных, паттерны, архитектура корпоративных приложений, объектно-ориентированный дизайн, принципы (SOLID, YAGNI, KISS, причём умение их использовать/не использовать на практике)
Хотя бы общее знание предметной области (не одной).
Инструменты для разработке на языке: отладка, профилирование, сборка, развёртывание.
Что приходится из этого проапдейтить при переходе с Java на Kotlin? Синтаксис. Ну может пару библиотек специально для Kotlin написанных посмотреть (ktor, exposed). Ну может научиться подключать модули к Spring, которые нужны для облегчения жизни Kotlin-истам. Ну просмотреть быстренько стандартную библиотеку Kotlin, увидеть, что она почти один-в-один является подмножеством стандартной библиотеки Java, плюс несколько расхождений (Sequence вместо Stream), плюс горстка удобств. Ну какие-то идиомы, которых нет в Java. Профайлеры те же. Отладчик тот же. Maven и Gradle те же. Поверьте, человеку с опытом в 10-15 лет в отрасли, всё указанное не так долго изучить. Вот перейти с Java на C++ или с Java на Python сложно. С Java на Haskell ещё сложнее.
благодаря этому они больше отвечают семантике "я строка в базе данных/элемент в очереди".
ИМХО, data классы и record-ы совсем не для этого были созданы. Как раз для строк в БД очень редко нужна специфическая семантика equals. Лично я считаю, что data class нужны ровно в двух ситуациях:
Объявить иммутабельные штуки вроде Color, Vector2/Vector3, Rect, Point, Complex и т.д.
Объявить кастомный составной ключ для Map/Set в случае, если Pair/Triple по какой-то причине не устраивают.
Собственно, поэтому мне на практике приходилось использовать data class-ы ОЧЕНЬ редко. Не понимаю, почему все бездумно лепят модификатор data на всякие DTO и entity (особенно на DTO)? Потому что есть ассоциация "данные == data"?
Это скорее не то, чтобы прекрасная идея, это back to roots. Потому что в своё время странная идея "а давайте писать тип переменной перед её именем" под влиянием языка, который (по другим причинам, а не из-за этой идеи) стал популярным, пошла в массы. Кажется, до народа дошло, и новые языки как раз возвращаются к нотации, появившейся чуть ли не раньше компьютеров (например Kotlin, TypeScript, Rust, Swift).
Для того, чтобы утвержать такое, надо вначале определиться, что мы называем ФП, потому что какого-то единого чёткого критерия нет. Если мы договоримся, что среди наших критериев есть ленивость, то да, с данным тезисом я соглашусь. Однако можно долго спорить, включать ли критерий ленивости в определение ФП. Всё-таки Ф — это про функции, а не про ленивость (иначе бы у нас был ЛП). А так получится, что только Haskell можно назвать полноценным ФП, а какие-нибудь OCaml или SML идут лесом.
Да условному синьёру не сложно выучить Kotlin. Может, он его уже отлично знает. Просто оказывается так, что из-за ряда факторов Kotlin вместо того, чтобы увеличивать эффективность сеньёра, снижает её. Лично для себя я просто выделил кейсы, когда Kotlin помогает, а когда мешает, и использую или не использую его в конкретной ситуации, исходя из этого понимания. Речь о том, что Kotlin на данный момент не может покрыть 100% (или даже 90%) ситуаций, когда он был бы однозначно лУчшим выбором, чем Java.
Можно ссылку? Просто я с ходу не нашёл. А для IDEA вообще ничего не надо настраивать — она сама при импорте проекта из maven/gradle обнаруживает annotation processors и подключает их.
Есть inline, tailrec
Никогда не понимал смысла в оптимизации хвостовой рекурсии. Есть же нормальные циклы. Это, конечно, очень увлекательное упражнение для ума — переписать всякие map/filter/fold на чисто функциональном языке, где циклов нет и нет ленивых вычислений, так, чтобы они были tailrec. Ещё было в студенческие годы не менее увлекательно написать библиотеку функций на SK комбинаторах. Однако на практике я вообще не встречал ни единой ситуации, когда написать функцию с хвостовой рекурсией было бы более просто и наглядно, чем написать банальный цикл.
Первое упоминание об IR появилось году так в 2017-м, когда его запилили для нужд Kotlin Native. С тех пор команда плавно переписывает все бэкэнды на IR, параллельно этот самый IR допиливая. К тому времени, как Kotlin 1.5 будет с нами и как все бэкэнды окончательно переделают в IR, и когда этот IR стабилизируется и появится стабильное же API для работы с ним из плагинов, и всё это ещё аккуратно поддержат в IDEA, вот тогда и поговорим (сколько ещё ждать, год, два три?). Тогда я признаю, что одной проблемой в Kotlin меньше. А пока мы имеем дело с тем, с чем имеем, и пункты 1 и 2 заставляют меня не переходить на Kotlin в тех проектах, где эти пункты являются критичными.
Вставлю свои 5 копеек, почему Kotlin может быть хуже Java
Нет адекватной замены annotation processors. В Java процессоры подтягиваются в IDEA и когда я жму build, они автоматом запускаются и (пере)генерируют нужный код. Если меняются классы, на которые смотрит процессор, то мне не надо запускать какую-то специальную тулзу — я просто жму run на нужной run configuration и магия сама работает. Конечно, из-за того, что Sun (а теперь Oracle) в своё время не продумали возможности работы с IDE, которая инкрементально запускает javac, это в редких случаях ломается, но в целом всё в разы лучше, чем в Kotlin, где единственная альтернатива — вручную запускать kapt.
Скорость работы компилятора. Сколько бы не говорили про то, что она приближается к работе javac, на деле там отставание не в 3 и не в 5 раз, а раз в 10-20. Это на реальном коде, с которым мне приходится работать, а не на каком-то синтетическом примере, для которого приводят бенчмарки для сравнения скорости компиляторов. Конечно, Kotlin умеет компилировать инкрементально, НО! Инкрементальная компиляция часто ломается во всяких интересных случаях (например, когда Kotlin модуль зависит от Java модуля, в котором сделали совсем небольшое невинное изменение).
Производительность сгенерированного кода. В подавляющем большинстве случаев это вообще не является проблемой, но есть специфические сценарии, где производительность важна и даже на Java пишут в C-стиле (потому что оптимизатор JVM слишком туп), и порой то, как генерирует код kotlinc, так же вносит свои 10-15% в снижение производительности. Особенно это важно для сред, которые плохо умеют в оптимизацию и имеют слабое железо (Android). Например, все JVM-декларации функциональных типов объявляют параметры Object и возвращаемое значение Object. Поэтому даже если функциональный тип в конкретном use site параметризован non-null kotlin.Int, мы всё равно наступим на boxing.
Nullability в некоторых случаях мешает. Например, мы можем какое-то время иметь частично инициализированную структуру, но потом мы в какой-то момент её достраиваем и точно знаем, что что-то — гарантированно не null. Java вообще никаких гарантий не даёт, так что все такие вещи делаются на уровне комментариев, документации в коде и т.д. Kotlin даёт гарантии, при этом!!! считается как бы дурным стилем и его принято стараться избегать. В этом ключе описанный мой случай приводит либо к обилию использования этого самого !!, либо к злоупотреблению lateinit, который может давать странные эффекты.
Отсутствие package private. Тут всё неодназначно, потому что в Java отсутствует internal, а порой его так же не хватает (но есть всякие OSGi, которые в каком-то смысле решают эту проблему).
Правило final by default. Конечно, про это уже 1000 раз говорили, для spring написали модуль, для jackson тоже, так же для Kotlin написали allopen. Однако, всё же всплывают то тут то там какие-то проблемы. Я понимаю благородные намерения авторов языка, однако, тут сложно сказать что лучше — сделать, как правильнее или как совместимее.
Короче, мне, как разработчику, не слишком интересно, запишу я код метода в 3, 5 или 10 строк. Мне важно, как удобно со всем этим будет работать в связке с другими инструментами из экосистемы Java. И у Kotlin с этим, пусть всё и хорошо, но не на 100% безоблачно.
Например, изрядное количество подробностей внутренней работы kotlinc скрыто внутри сгенерированных файлов классов, представляющих из себя аннотации @Metadata с бинарными данными (байтовыми массивами, разрешёнными в аннотациях) внутри. Насколько мне известно, эти данные не описаны ни в каких публичных спецификациях.
Кстати, их не то, чтобы совсем сложно распарсить. Это на самом деле protobuf, proto-файлы находятся в репозитории Kotlin. Их можно аккуратненько скопировать себе и сгенерировать код, который парсит данные. Там остаются кое-какие нюансы, которые можно узнать, почитав код компилятора Kotlin, но в целом задачу "прочитать метаданные Kotlin" я в своё время осилил.
В тех примерах, что я смотрел, компиляторы C++ стараются избегать передачи через стек, используя locals
Так это и возможно сделать только если параметр передаётся в виде value. А если в виде ссылки, то единственный сценарий, когда, КМК, это можно оптимизировать — если компилятор решил заинлайнить функцию. Во всех иных ситуациях если вы явно попросите у C++ передать указатель или ссылку на переменную в стеке, то он и передаст указатель или ссылку соответственно. Это не особо возможно оптимизировать.
Если все-таки стек нужен, то считается, что указатель на вершину стека хранится по смещению 4 в памяти
Что по сути и есть тот самый медленный shadow stack взамен быстрого нативного.
Ну и хотел бы так же добавить, что в итоге идея "скомпилировать байт-код в JS", хотя и кажется людям, далёким от всей этой кухни, каким-то ужасным хаком, на деле оказывается гораздо более жизнеспособной, чем компиляция в Wasm.
Как автор компилятора, который может перегонять байт-код JVM в WASM могу вставить свои 5 копеек.
Нет доступа к стеку. Делали так из благих побуждений — безопасность и простота реализации виртуальной машины. На деле, в C++ вполне обычная ситуация, когда надо вызвать функцию, передав ей указатель на локальную переменную. Честно говоря, что генерируют компиляторы C++ в Wasm, я не смотрел. Да и в Java так делать нельзя. Зато в Java есть GC, у которого roots находятся в стеке. Как я это обошёл? Завёл shadow stack прямо в хипе. Да, я придумал хитрый алгоритм, который вычисляет для каждого call site минимальный объём обновлений shadow stack. Но всё равно это медленно.
Нельзя поиграться с memory protection. Есть очень много сценариев использования оного. Самый простой — проверка указателей на null. В реализациях C++ или Java здорового человека принято первую страницу делать недоступной и поэтому при попытке записать или прочитать по адресу 0, CPU бросает исключение, которое можно поймать в виде, например, сигнала unix. В Wasm такие трюки не работают и приходится просто перед каждым dereference (например, чтением поля объекта) вставлять проверку. Кстати, т.к. доступа к стеку нет, то приходится в shadow stack маркировать любой такой доступ, чтобы при выбросе исключения правильно воссоздать stack frame.
Казалось бы, нам обещают GC и exception handling в будущих версиях. Но вот я, честно говоря, слабо верю в то, что какая-то прибитая гвоздями спецификация GC сможет учесть разнообразие поведения разных VM в различных экзотических ситуациях, например, с разнообразными weak reference.
Ещё одна претензия к черновику GC: приходится дублировать заголовок. Для нужд GC в Wasm необходимо объявлять типы данных и при аллокации объекта (tuple) в управляемом хипе указывается этот тип. Понятное дело, что физически при этом какое-то количество байт будет отведено на указатель на тип данных. Далее, чтобы реализовать виртуальные вызовы, мне так же надо в объекте хранить указатель на virtual table. Получается, у меня двойной заголовок объекта, потребляющий в два раза больше памяти. Это в то время, как у нормальных людей заголовок объекта сразу и vtable описывает и layout объекта для GC.
Отсутствует вообще какая-либо стандартная библиотека или хотя бы набор инструкций для некоторых важных операций, вроде копирования участка памяти, обнуления участка памяти, работы с плавающими числами (например, какой-нибудь isNaN) и т.д.
Какой-то очень мутный застрявший процесс. Вот вышла несколько лет назад спека 1.0 и дальше комитет только заседает и заседает, генерирует какие-то кривые черновики, а воз и ныне там. Похоже на ситуацию с застрявшей спекой XHTML в своё время.
В любом случае, всё значачительно выгоднее покупать жильё в ипотеку России по ставке в 9%, чем в Германии со ставкой 2%
Зачем senior developer-у с его почти европейской зарплатой брать квартиру стоимостью в 150К евро в ипотеку на 30 лет, если она при таком раскладе выплачивается лет за 10 максимум (мы же не дураки, чтобы не накопить хоть какого-нибудь первоначального взноса)?
Дело даже не в налогах. Дело в том, что цены на квартиры в каком-нибудь Мюнхене начинаются от условных 500К евро, а в Питере за какие-нибудь жалкие 150К евро (в доковидные времена, сейчас всё сильно поменялось) можно купить что-то приличное в не очень многоэтажном недалеко от центра. При этом и сама ипотека быстрее платится, т.к. в целом дешевле жизнь. При этом работая из России можно получать почти европейские зарплаты. Я понимаю, что при этом в Германии несравнимо лучше городская среда, но в ней я просто не имею выбора: либо покупай квартиру по конским ценам, либо не покупай вовсе. Мне известны контраргументы: да кто же в Германии живёт в своём жилье? Ну не знаю, меня, например, очень заботит вопрос, где я буду жить после пенсии. Хотелось бы в этом вопросе чуть больше понадеяться на свои силы, а не делегировать это государству.
Вот не знаю, совсем не могу согласиться! Я не работал в других отраслях и не могу про них судить - может там всё в разы хуже. И не работал прямо в других странах. А так, в уютной айтишечке если работаешь с иностранными коллегами и всего лишь переписываешься/перезваниваешься с ними, тоже чувствуются национальные особенности, менталитет. Не то, чтобы это было какой-то непреодалимой проблемой, но тем не менее дискомфорт ощущается и при прочих равных работать с русскими коллегами в разы проще.
Всё так. У меня по этому поводу как раз синдром самозванца. Мне постоянно присылают непрошенные вакансии на Java/Kotlin (и почему-то даже .NET) senior developer. Я читаю список требований и понимаю, что вообще мои текущий знания нерелевантны. И что мне делать, если меня внезапно попрут с текущего места? Остаётся только надеяться, что рано или поздно я выйду на людей, которых впечатлит мой опыт и мои скилы и которые поверят, что вспомнить Spring, которого я лет 8 уже не трогал, я быстро смогу. Но учитывая конвейер найма, не думаю, что это случится прямо совсем быстро. Останется мне с апломбом приходить и заявлять, что я синиор и продаю не знания какого-то там фреймворка, а общеинженерные навыки. Интересно, вот действительно, что важнее: понимать, как в очередном фреймворке используются корутины Kotlin или глубокое понимание устройства этих самых корутин и причин именно такого их дизайна в языке и рантайме языка?
Ещё такой момент, что в любом случае в каждой компании есть своя культура, своя специфика. В неё всё равно придётся вникать, и порой там пласты будут побольше, чем просто разобраться в каком-нибудь стеке. Так что совокупный вклад стека в онбординг будет не таким уж большим.
Конечно, это работает, если человек переходит с бэкэнда на бэкэнд - стеки хоть и разные, но общие подходы в принципе уже давно отработаны и ничего принципиально нового так же не то, чтобы очень много появляется. А вот с бэкэнда на фронтэнд перейти или вообще попасть в бэкэнд из чего-то где нет такого разделения (например, из разработки компиляторов) будет сложнее, т.к. нет понимания, какие задачи возникают и какие бывают подходы к их решению.
И главное: вот нанимаешь такой человека с хорошим резюме. И вещи он правильные на собеседовании говорит. А как нанимаешь - он сидит и
пишет комментарии на Хабреничего не делает. Так что в этом смысле конкретный стек вообще вещь не принципиальная.Увы, но нет. Пока с самой спекой wasm не сделают чего-то, никакой оптимизатор погоды не сделает. Есть множество причин, почему код wasm работает медленно, например:
При любом доступе к куче делается bound check. Это в какой-то степени компенсируется оптимизатором, но очень часто у оптимизатора просто не хватает информации, чтобы понять, что проверку можно убрать
В Wasm нельзя получить указатели на локальные переменные. Это значит, что если вы пишете код: "int a = 0; foo(&a);", то, кроме небольшого числа тривиальных кейсов, которые может распознать оптимизатор, копилятору придётся запрятать a в shadow stack, что и само по себе не быстро, а учитывая, что shadow stack находится в куче, доступ к которой проверяется, выходит совсем уж печально
В Wasm нельзя походить по стеку. Это серьёзно ограничивает возможности по эффективной реализации GC и исключений. Что GC, что исключения, уже давно обещают, но воз и ныне там. И судя по черновикам, тот же GC (точнее, модель "кортежей") получится куцым и малопригодным для реальных нужд. С исключениями ситуация вроде получше (мне и черновик больше нравится, и шансов у него добраться до финальной спеки выше), так что возможно в каком-то обозримом будущем для кода на C++, активно использующем исключения, мы действительно увидим существенный прирост производительности.
В Wasm нельзя делать трюки с memory protection, которые так же можно использовать, чтобы генерировать segfault при разыменовании нулевого указателя или переполнении стека.
Ну и т.д.
Ваша методика бенчмаркинга не выдерживает никакой критики. Движкам с JIT необходимо прогреться, собрав статистику по выполнению кода и затем оптимизировав его. Соответственно, перед замерами тестируемый код надо погонять в цикле. Далее, сам замеряемый код так же необходимо прогнать несколько раз. То же самое желательно делать и с Wasm, т.к. никто не гарантирует, что конкретная среда не делает JIT. Далее, сам код - нарочито неоптимальный. По сути вы тут тестируете как быстро среда делает вызовы функций. Было бы интереснее что-то повнушительнее: рейтрейсинг, deflate, компиляция C, физический движок. Я понимаю, что для вводной статьи это слишком страшные вещи, так что хотя бы можно было потестировать на трёх версиях fib: вашей (рекурсивная, экспоненциальная сложность), рекурсивная с мемоизацией, итеративная.
Ну почему же. Вот, например. Правда, кросплатформенность и без Kotlin бывает, на обычной Java. Для iOS был MOE, а сейчас GraalVM. На Android и на сервере нативно работает. На web — TeaVM. Правда, на сервере не надо запускать весь код клиента, достаточно только части, отвечающей за OT.
Думаю, некорректно сравнивать корутины и Loom. Корутины — это языковой механизм, который позволяет в том числе на уровне языка легко запилить аналог Loom. Но не обязательно их для этого использовать. Например, с их помощью можно делать генераторы последовательностей.
Ахаха, вы это скажите сотрудникам Kotlin team, которые ловеринги пишут не путём "я пересоздам все IR элементы от рута до текущего элемента", а путём старого доброго изменения мутабельного свойства. Думаете, они там дураки сидят, которые не знают всех преимуществ иммутабельных данных и не могут в ФП? Боюсь, если бы они делали IR иммутабельным, kotlinc работал ещё раз в 100 дольше, чем он работает сейчас.
Так если это иммутабельный класс, то бога ради. Я просто вижу, как на вполне мутабельных расставляют. Впрочем, в Kotlin не всё так хорошо с иммутабельностью. Вот вам маленькая задачка от меня. Что выведет следующий код?
Синьоры стали синьорами не потому, что все свои 10-15 лет в индустрии потратили на изучение синтаксиса и идиом конкретно взятого языка. Они изучали такие вещи как:
Что приходится из этого проапдейтить при переходе с Java на Kotlin? Синтаксис. Ну может пару библиотек специально для Kotlin написанных посмотреть (ktor, exposed). Ну может научиться подключать модули к Spring, которые нужны для облегчения жизни Kotlin-истам. Ну просмотреть быстренько стандартную библиотеку Kotlin, увидеть, что она почти один-в-один является подмножеством стандартной библиотеки Java, плюс несколько расхождений (Sequence вместо Stream), плюс горстка удобств. Ну какие-то идиомы, которых нет в Java. Профайлеры те же. Отладчик тот же. Maven и Gradle те же. Поверьте, человеку с опытом в 10-15 лет в отрасли, всё указанное не так долго изучить. Вот перейти с Java на C++ или с Java на Python сложно. С Java на Haskell ещё сложнее.
ИМХО, data классы и record-ы совсем не для этого были созданы. Как раз для строк в БД очень редко нужна специфическая семантика equals. Лично я считаю, что data class нужны ровно в двух ситуациях:
Собственно, поэтому мне на практике приходилось использовать data class-ы ОЧЕНЬ редко. Не понимаю, почему все бездумно лепят модификатор data на всякие DTO и entity (особенно на DTO)? Потому что есть ассоциация "данные == data"?
Это скорее не то, чтобы прекрасная идея, это back to roots. Потому что в своё время странная идея "а давайте писать тип переменной перед её именем" под влиянием языка, который (по другим причинам, а не из-за этой идеи) стал популярным, пошла в массы. Кажется, до народа дошло, и новые языки как раз возвращаются к нотации, появившейся чуть ли не раньше компьютеров (например Kotlin, TypeScript, Rust, Swift).
Для того, чтобы утвержать такое, надо вначале определиться, что мы называем ФП, потому что какого-то единого чёткого критерия нет. Если мы договоримся, что среди наших критериев есть ленивость, то да, с данным тезисом я соглашусь. Однако можно долго спорить, включать ли критерий ленивости в определение ФП. Всё-таки Ф — это про функции, а не про ленивость (иначе бы у нас был ЛП). А так получится, что только Haskell можно назвать полноценным ФП, а какие-нибудь OCaml или SML идут лесом.
Да условному синьёру не сложно выучить Kotlin. Может, он его уже отлично знает. Просто оказывается так, что из-за ряда факторов Kotlin вместо того, чтобы увеличивать эффективность сеньёра, снижает её. Лично для себя я просто выделил кейсы, когда Kotlin помогает, а когда мешает, и использую или не использую его в конкретной ситуации, исходя из этого понимания. Речь о том, что Kotlin на данный момент не может покрыть 100% (или даже 90%) ситуаций, когда он был бы однозначно лУчшим выбором, чем Java.
Можно ссылку? Просто я с ходу не нашёл. А для IDEA вообще ничего не надо настраивать — она сама при импорте проекта из maven/gradle обнаруживает annotation processors и подключает их.
Никогда не понимал смысла в оптимизации хвостовой рекурсии. Есть же нормальные циклы. Это, конечно, очень увлекательное упражнение для ума — переписать всякие map/filter/fold на чисто функциональном языке, где циклов нет и нет ленивых вычислений, так, чтобы они были tailrec. Ещё было в студенческие годы не менее увлекательно написать библиотеку функций на SK комбинаторах. Однако на практике я вообще не встречал ни единой ситуации, когда написать функцию с хвостовой рекурсией было бы более просто и наглядно, чем написать банальный цикл.
Первое упоминание об IR появилось году так в 2017-м, когда его запилили для нужд Kotlin Native. С тех пор команда плавно переписывает все бэкэнды на IR, параллельно этот самый IR допиливая. К тому времени, как Kotlin 1.5 будет с нами и как все бэкэнды окончательно переделают в IR, и когда этот IR стабилизируется и появится стабильное же API для работы с ним из плагинов, и всё это ещё аккуратно поддержат в IDEA, вот тогда и поговорим (сколько ещё ждать, год, два три?). Тогда я признаю, что одной проблемой в Kotlin меньше. А пока мы имеем дело с тем, с чем имеем, и пункты 1 и 2 заставляют меня не переходить на Kotlin в тех проектах, где эти пункты являются критичными.
Вставлю свои 5 копеек, почему Kotlin может быть хуже Java
Короче, мне, как разработчику, не слишком интересно, запишу я код метода в 3, 5 или 10 строк. Мне важно, как удобно со всем этим будет работать в связке с другими инструментами из экосистемы Java. И у Kotlin с этим, пусть всё и хорошо, но не на 100% безоблачно.
Кстати, их не то, чтобы совсем сложно распарсить. Это на самом деле protobuf, proto-файлы находятся в репозитории Kotlin. Их можно аккуратненько скопировать себе и сгенерировать код, который парсит данные. Там остаются кое-какие нюансы, которые можно узнать, почитав код компилятора Kotlin, но в целом задачу "прочитать метаданные Kotlin" я в своё время осилил.
Так это и возможно сделать только если параметр передаётся в виде value. А если в виде ссылки, то единственный сценарий, когда, КМК, это можно оптимизировать — если компилятор решил заинлайнить функцию. Во всех иных ситуациях если вы явно попросите у C++ передать указатель или ссылку на переменную в стеке, то он и передаст указатель или ссылку соответственно. Это не особо возможно оптимизировать.
Что по сути и есть тот самый медленный shadow stack взамен быстрого нативного.
Ну и хотел бы так же добавить, что в итоге идея "скомпилировать байт-код в JS", хотя и кажется людям, далёким от всей этой кухни, каким-то ужасным хаком, на деле оказывается гораздо более жизнеспособной, чем компиляция в Wasm.
Как автор компилятора, который может перегонять байт-код JVM в WASM могу вставить свои 5 копеек.
Нет доступа к стеку. Делали так из благих побуждений — безопасность и простота реализации виртуальной машины. На деле, в C++ вполне обычная ситуация, когда надо вызвать функцию, передав ей указатель на локальную переменную. Честно говоря, что генерируют компиляторы C++ в Wasm, я не смотрел. Да и в Java так делать нельзя. Зато в Java есть GC, у которого roots находятся в стеке. Как я это обошёл? Завёл shadow stack прямо в хипе. Да, я придумал хитрый алгоритм, который вычисляет для каждого call site минимальный объём обновлений shadow stack. Но всё равно это медленно.
Нельзя поиграться с memory protection. Есть очень много сценариев использования оного. Самый простой — проверка указателей на null. В реализациях C++ или Java здорового человека принято первую страницу делать недоступной и поэтому при попытке записать или прочитать по адресу 0, CPU бросает исключение, которое можно поймать в виде, например, сигнала unix. В Wasm такие трюки не работают и приходится просто перед каждым dereference (например, чтением поля объекта) вставлять проверку. Кстати, т.к. доступа к стеку нет, то приходится в shadow stack маркировать любой такой доступ, чтобы при выбросе исключения правильно воссоздать stack frame.
Казалось бы, нам обещают GC и exception handling в будущих версиях. Но вот я, честно говоря, слабо верю в то, что какая-то прибитая гвоздями спецификация GC сможет учесть разнообразие поведения разных VM в различных экзотических ситуациях, например, с разнообразными weak reference.
Ещё одна претензия к черновику GC: приходится дублировать заголовок. Для нужд GC в Wasm необходимо объявлять типы данных и при аллокации объекта (tuple) в управляемом хипе указывается этот тип. Понятное дело, что физически при этом какое-то количество байт будет отведено на указатель на тип данных. Далее, чтобы реализовать виртуальные вызовы, мне так же надо в объекте хранить указатель на virtual table. Получается, у меня двойной заголовок объекта, потребляющий в два раза больше памяти. Это в то время, как у нормальных людей заголовок объекта сразу и vtable описывает и layout объекта для GC.
Отсутствует вообще какая-либо стандартная библиотека или хотя бы набор инструкций для некоторых важных операций, вроде копирования участка памяти, обнуления участка памяти, работы с плавающими числами (например, какой-нибудь isNaN) и т.д.
Какой-то очень мутный застрявший процесс. Вот вышла несколько лет назад спека 1.0 и дальше комитет только заседает и заседает, генерирует какие-то кривые черновики, а воз и ныне там. Похоже на ситуацию с застрявшей спекой XHTML в своё время.