Хабрахабр

«Мы даже не пытаемся запустить старый код, такой задачи у нас не стоит в принципе» — Роман Елизаров о разработке Kotlin

Если хочешь в чем-то разобраться — учись сразу у лучших. Сегодня на мои вопросы отвечает бог корутин и concurrency, Рома elizarov Елизаров. Мы поговорили не только о Kotlin, как вы могли бы подумать, но ещё и о куче смежных тем:

  • Golang и горутины;
  • JavaScript и его применимость для серьезных проектов;
  • Java и Project Loom;
  • олимпиадное программирование на Kotlin;
  • как правильно обучаться программированию;
  • и другие волнующие вещи.

Давай вначале пару слов о себе. Привет. Ты давно занимаешься Kotlin?

В 2010 году Kotlin начинался как проект в JetBrains, где я в тот момент еще не работал. У меня с Kotlin давняя история. Изначально язык дизайнился для решения своих проблем, ведь у JetBrains своя большая база кода на Java, с понятными проблемами, которые в коде постоянно есть, и хотелось сделать язык для себя, чтобы свой код писать приятней, эффективней, с меньшим количеством ошибок. Но Макс Шафиров (он тогда занимался Kotlin и был одним из инициаторов этого движения внутри JetBrains) пригласил меня стать внешним экспертом и посмотреть на дизайн, прокомментировать. Естественно, это быстро переросло в идею, что раз у нас такие проблемы — значит, и у других такие проблемы есть, и им нужно было подтверждение от других людей, что они идут правильным путем. Просто провести у себя модернизацию.

Про nullability — это я настоял, что этим надо заниматься, потому что мне в тот момент было очевидно, что если ты пишешь на Java, там много проблем, но nullability — это основная беда, на которую постоянно наталкиваешься. Меня пригласили как эксперта, чтобы я посмотрел и сверил то, что происходит, с тем, что надо.

Я всю жизнь занимаюсь соревнованиями, но сам уже тогда активно не участвовал. В самой работе команды я не участвовал, просто периодически поглядывал, участвовал в соревнованиях на Kotlin (Kotlin Cup). А в Kotlin Cup я принял участие и, так как он не собрал широкую аудиторию, я легко вышел в финал. Например, я бы не вышел в финал соревнований вроде Facebook Hacker Cup, форма не та из-за того, что в соревнованиях уже не участвую на постоянной основе.

С тех пор команда проделала огромную работу. На тот момент (2012-2013 гг.) Kotlin представлял собой грустное зрелище с точки зрения тулинга, потому что там всё тормозило. 0 и до того, как Google официально признал язык. Я пришел в команду два года назад, сразу после релиза 1. Поэтому я хорошо представлял себе проблемные места — что надо чинить и что у людей болит. В команде я занялся всякой асинхронностью и корутинами, просто потому что так вышло, что у меня подходящий опыт, я много в DevExperts занимался всякими разными большими энтерпрайзными системами, и там много асинхронности и коммуникации. Болит у всех. Это очень хорошо легло на нужды Kotlin, потому что болит не только у нас. Я до сих пор занимаюсь котлиновскими библиотеками, и основной наш фокус — на всякие connected-приложения и асинхронность.
Даже в JVM занялись Project Loom, что как бы намекает, что болит у всех.

То есть ты занимаешься в основном библиотеками, не компилятором и вот этим всем?

Общаюсь с ребятами, и наша команда библиотечная догфудит все, что делают в компиляторе. Нет, я компилятором занимаюсь постольку-поскольку. Мы являемся и заказчиками, мы очень много фич-риквестов создаем, когда натыкаемся на какие-то недостатки, и мы — тестеры первой линии всего нового, что выкатывается.

Получается, если зайти в YouTrack, пофильтровать по тебе, можно много чего интересного обнаружить.

Да, можно найти кучу всяких задач, потому что я постоянно на что-то наталкиваюсь.

Его сделал парень, который сделал Quasar. Ты упомянул Project Loom. Можешь рассказать что-нибудь про него?
Cо стороны это выглядит очень забавно, я как раз хотел на Хабру писать статью про Loom.

Корутины и асинхронное программирование нужны всем. Видел презентацию, идея понятная. Они уже потом подпилили под себя. Например, на прошлом JPoint ребята из Alibaba рассказывали, как они хакнули JVM и прикрутили себе файберы хотспот, просто накатив туда патчик, который даже не они написали, а какие-то ребята до них. Очень рекомендую.
Замечательный доклад.

А ты рекомендуешь так делать?

Каждый большой энтерпрайз, выше какого-то размера, когда у тебя начинает работать несколько тысяч человек (а для кого-то и меньше), мейнтейнит свой хак OpenJDK. Так делать в энтерпрайзах приходится. Не то чтобы я это рекомендую, но приходится. И конечно, если у тебя есть бизнес-критичные юзкейсы, то почему бы и не хакнуть что-то под себя, не вижу в этом никакой большой проблемы. Это, собственно, говорит о том, что людям надо, что назрело. Если в HotSpot нет легковесных потоков, то что делать? Тот факт, что они должны как-то поддерживаться в JDK, давно назрел, и в этом смысле я не сомневаюсь, что когда Loom рано или поздно дойдет по продакшна, это будет востребовано. И фидбэк, который мы получаем по корутинам, тоже говорит о том, что да, назрело, людям нужны легковесные потоки, у людей вагон юзкейсов для легковесных потоков. Есть люди, которые даже ради этого патчат HotSpot.
Есть люди, которым это надо.

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

И веб-сервер, и application-сервер, и бэкенд. Это довольно типичная проблема. Я с таким же работал в DevExperts: сервисы под нагрузкой, тебе приходят запросы, которые ты не сам ведь обрабатываешь — в современном мире у тебя всё connected. Если ты посмотришь ту же презентацию Алибабы, почему и понадобилось это дело, то у них не веб-сервер, у них классическая энтерпрайзная архитектура, у них на бэкенде на Java написаны всякие сервисы, эти сервисы находятся под нагрузкой. И если эти сервисы тормозят, то у тебя много потоков ждет. И вот этот запрос ты не сам обрабатываешь, а еще 100500 всяких других сервисов вызываешь и ждешь, пока они ответят. И у тебя получается просто из-за какой-то ерунды следующее: один сервис, который ты используешь, тормозит, и куча потоков стоит и ждет. Ты не можешь себе позволить иметь десятки тысяч этих ждущих потоков. И сейчас это очень большая проблема.

В том же Alibaba, решение, которое они заимплементили — оно вообще тупое из всех тупых. Одна из причин, почему люди массово мигрируют на Go — не потому, что язык хороший, а потому что там легковесные потоки из коробки, и такой проблемы уже нет: горутины могут ждать, и они ничего не стоят. Они экономят физический поток, но не экономят стеки. Они не очень легковесные в том смысле, что они каждой корутине выделяют один большой стек по 2 мегабайта, хакнув HotSpot, чтобы можно было эти стеки переключать. Ребята из Loom затеяли нечто более глобальное. И для них решение работает — оно, кстати, очень простое, у них патч HotSpot, насколько я понимаю, не очень большой. В прототипе текущий стек через HotSpot проходит, его копируют в маленькую хиповую структуру. Они решили сэкономить не только на физических потоках, но и на стеке, чтобы не тратить 2 мегабайта на поток. И могут дальше этот физический стек переиспользовать для других целей.

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

Что в итоге из этого получится — очень сложно сказать. Да, там вагоны хаков и оптимизаций. Изнутри нативного вызова ты уже не можешь скопировать стек нативного вызова. Потому что на примере подхода с копированием, сразу возникает следующая проблема: а что делать с нативными вызовами? Нативный, не нативный — какая разница, ты просто тот стек отцепил совсем и оставил его в покое, подцепил другой стек, всё работает. В подходе Alibaba такой проблемы нет. Например, как это в Go реализовано — там совсем другой механизм. И тут рано говорить, что получится или не получится, с этим нативным стеком иногда можно жить, иногда нельзя — на этом этапе рано сказать. Соответственно, когда гошный рантайм вызывает функцию, он смотрит, сколько нужно стека. Пока ты выполняешь гошный код, используются маленькие гошные стеки. Если, соответственно, ты делаешь нативный вызов, то они уже берут какой-то большой нативный стек из некоего пула и используют его.
Если текущего стека не хватает, он перевыделяет — увеличивает размер выделенного стека.

И для гошного кода тоже?

Они просто могут переключиться на большой нативный стек, если надо вызвать какую-то внешнюю функцию, для которой непонятно, сколько стека надо. Неважно. Тут совсем другой подход. А когда ты выполняешь гошный код, известно, сколько стека надо, поэтому мы можем выполнять его на маленьком стеке. По сути, между этими подходами большой разницы нет до тех пор, пока ты эпизодически засыпаешь. Не копируем, а сразу выполняем на маленьком стеке.

Что подходит? Нам постоянно задают вопрос: «Что быстрее? Наша задача заключается в том, чтобы это работало под обычным JVM. Как вы в корутинах это делаете?» Мы в корутинах не хакаем JVM. Там свой ART, который тоже о корутинах ничего не знает. И чтобы на Android тоже работало. Берем его, когда он уже засаспендится. И поэтому, естественно, нам приходится ручками генерировать байткод, который делает что-то очень похожее на копирование стека, который делает Loom, только мы это делаем в байткоде. Мы не на рантайме, который бы это за нас делал, у нас сгенерирован байткод, который это делает. Берем стек, разматываем и копируем в хип. Из-за того, что мы не делаем рантайм, естественно, у нас от этого больше оверхеда. Он сохраняет и восстанавливает состояние корутины. С другой стороны, если ты корутины используешь для асинхронного программирования, то тебе надо заснуть, если ты ушел ожидать ответа от какого-то сервиса, а послать запрос в какой-то сервис так дорого, что весь оверхед на копировании стека вообще никого не волнует — медленный он у тебя или быстрый — вообще становится неважно. В рантайме ты можешь все сделать быстрее. У нас на корутинах в Котлине это замечательно скейлится, как и показано в прототипе Project Loom. Да, если ты это используешь именно для асинхронного программирования.

С одной стороны, вроде бы и неудачно, а с другой — наоборот. Другое отличие — так как мы в Котлине вынуждены делать это в байткоде, то у нас есть такой интересный побочный эффект. Нужно функции, которые могут заснуть, помечать модификатором suspend — явно пометить, что функция может приостановиться и чего-то ждать, что она долгая. Заключается он в следующем: нельзя усыпить произвольную функцию. В решении от Alibaba то же самое — ты можешь у любого потока отобрать стек. С одной стороны, в Loom тебе это не нужно, потому что рантайм может усыпить что угодно. Наплоди еще горутин и делай. Или в Go — там всё можно засаспендить, любой код может уснуть. Ты как бы программируешь как раньше, только теперь треды называются файберами и стали очень дешевыми. С одной стороны, этот подход очень похож на программирование с тредами. Как сделать так, чтобы старый код, который написан с тредами, прям совсем из коробки завелся на файберах — не очевидно, и что у них получится — никто не знает. Если внимательно посмотреть презентацию того же Loom, выясняется, что файберы и треды — это всё-таки разные вещи. И в Go та же самая проблема — когда хардварные thread ID не экспозятся, писать какой-то high performance-алгоритм становится нетривиально.
Там начинаются проблемы: а что делать с дэдлоками, что делать с кодом, который соптимизирован на thread locals, опять же какие-то хэши свои локальные имеет или по thread ID хитро какие-то перформанс-оптимизации делает.

А в Котлине такого нет?

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

Да, нужен thread local, но он может их узнать. У нас в нашей модели нет никакого старого кода, только новый, который изначально готов к тому, что сегодня он на одном треде, завтра на другом, и если ему, например, нужно узнать, какой сейчас тред, он это узнает. Если он хочет, чтобы эти локалы путешествовали с ним, для этого есть другой механизм, корутинный контекст, где он может хранить свои вещи, которые будут вместе с корутиной путешествовать с треда на тред. Однако он должен быть готов к тому, что сегодня thread locals одни, а завтра — другие. Это, в каком-то смысле, нам упрощает жизнь, потому что мы не пытаемся старый код поддерживать.

Если раньше я смотрю на какой-то метод в своем коде, getЧтоНибудь, непонятно, этот метод быстро работает и возвращается сразу или пойдет в сеть и может час работать — я могу только документацию почитать и понять, как быстро он будет работать. А с другой стороны, мы заставляем человека явно подумать над своим API, сказать: вот я пишу функцию на Kotlin с корутинами. С Kotlin-корутинами мы даем гарантированный языком механизм с модификатором suspend. А может, сейчас он быстро работает, а завтра придет программист Вася Пупкин и сделает так, что он теперь ходит в сеть. Есть модификатор suspend, значит, эта функция какая-то асинхронная, она пойдет надолго в сеть. Я когда сам работаю с корутинами, смотрю на какую-то функцию, если не вижу модификатора suspend, значит, она быстро работает, всё локально делает. Это помогает сразу избежать тупых ошибок, когда я где-то забылся и где-то в коде вызвал что-то долгое, не подозревая об этом. И это помогает делать самодокументирующийся API, чтобы мы сразу видели, что нас ожидает.

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

Но есть же часть вещей, которые сложно запретить, например, какой-нибудь сетевой IO, файловый.

Вот файловый IO — сложно. Нет, сетевой IO как раз запретить достаточно легко. Очень редкое приложение так много работает с IO, что для него становится проблемой тот факт, что это занимает так много времени. Но здесь опять тонкий момент: для большинства приложений файловый IO — это быстрая вещь, и поэтому совершенно нормально, что он работает синхронно. Но если конкретно в твоем кейсе просто какое-то очень долгое вычисление, вроде не асинхронное, но просто жрет кучу времени CPU, и ты не хочешь этим самым блокировать какие-то свои другие thread-пулы, мы предоставляем простой понятный механизм: ты заводишь отдельный thread pool для своих тяжелых вычислений, и вместо того, чтобы писать обычную функцию, которая fun computeSomething(), и писать в документации «Чуваки, аккуратно, эта функция может работать очень долго, поэтому внимание — не используйте ее где попало, не используйте в UI», мы предлагаем более простой механизм. И здесь мы даем человеку возможность выбрать: ты можешь у нас напрямую делать файл IO и не париться, потому что оно будет блокировать, что происходит (потому что обычно это быстро). Это очень удобно: пользователю не надо больше парить мозг: он сразу видит suspend, знает, что этот вызов его тред не блокирует, и он может совершенно спокойно вызывать её из UI-потока и так далее.
Ты просто пишешь эту функцию как suspend fun computeSomething(), а для её реализации используешь специальную библиотечную функцию withContext, которая перекидывает вычисление на указанный тобой специальный thread pool.

Это правильный separation of concern: пользователя не парит, как оно реализовано, а тот, кто реализует, может правильно перекинуть на тот пул, на который нужно, и правильно распределить вычислительные ресурсы в своем приложении. Она уже внутри переключится на нужный поток, а его поток не заблокируют. Надо писать меньше документации, компилятор больше проверит и поправит.
На практике это оказывается очень удобно с точки зрения стиля программирования.

Может ли кто-то сломать thread pool или вломиться в чужие данные?
Я думаю, насколько это безопасно.

От кривых рук тяжело защитить. Естественно, всё возможно. Вопрос в том, что компилятор должен помогать писать правильный код. Понятно, что сколько бы мы ни писали в компиляторе всякие системы типов и проверки, всегда всё можно сломать. Мы специально не включаем какие-то фичи в язык. К сожалению, мечта запретить писать плохой код утопична. Но и любую хорошую фичу, которая есть в Котлине, можно использовать не по назначению массой разных способов. В Котлине нет каких-то вещей из Java, если про них известно, что они в основном используются не по назначению и код с ними в основном плохой. Язык можно абьюзить по-разному. Вариантов нет, к сожалению. От кривых рук не защитишься никак.

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

От каких вопросов?

Без дженериков.
Например, один вопрос пережил двух человек: почему в Котлине нет raw types?

Потому что всегда можно написать звёздочку.

А оно при этом будет совместимо с Java?

Да.

List, например.
То есть у тебя есть какой-то джавовый метод, который требует что-то без дженерика.

Можешь List со звёздочкой передать, можешь со строкой. Если есть такой джавовый метод без дженерика, ты туда можешь передать любой List. Если он возвращает тебе raw List, то в Котлине ты получишь некий platform type, который ты можешь закастить, например, к List со звездочкой. Котлин позволит тебе в джавовый метод передать любую дичь. Была Java, в которой не было генериков, теперь есть генерики, и чтобы люди не мучались, чтобы им не нужно было по всему коду эти угловые скобочки проставлять, была сделана эта специальная фича, raw type. Потому что raw type, по сути, в Java сделан не от хорошей жизни, а чтобы было проще мигрировать. Есть только новый код, в котором все типы — генерики. Когда делали Котлин, такой проблемы не было — не было никакого старого кода, в котором генерики не указаны. Она Котлину не нужна, так как тут нет migration story.
Поэтому проблема миграции со старого кода без генериков на новый код с генериками — её не существует, и нет смысла делать в языке такую фичу, как raw types.

Как работает кастинг platform type к котлиновскому?

Это проще рассмотреть на примере nullable-типов. Платформенные в Котлине считаются flexible. В Котлине непонятно, там String или nullable String — это два разных типа. Вот у тебя есть джавовский код, который возвращает String. Поэтому Kotlin считает, что он может быть и такой, и сякой. И мы ведь не знаем, какой String возвращается из джавовского метода — nullable или не-nullable. То же самое и здесь, когда ты получаешь какую-то слаботипизированную дичь от Java, ты на стороне Котлина всегда можешь указать просто более специфичный и правильный тип.
И разрешает тебе его присвоить как в String, так и в nullable String.

А если ты указал неправильно?

Flexible не значит что угодно, это не dynamic. Если ты указал совсем неподходящий, то у тебя компилятор, естественно, ругнется, потому что он должен подходить. Flexible указывает на диапазон — то, что сейчас вернула Java, ты потом можешь присваивать и в String, и в nullable String, но не в int.

Там всё-таки есть какое-то прямое перечисление возможного, и оно там матчится.

То же самое с коллекциями. То же самое с сырыми типами, похожий механизм, но немного посложнее. У тебя бывает List и MutableList. В Котлине коллекции разделяются на read only и mutable. Котлин более строго типизирован, чем Java. Если тебе Java возвращает List, то фиг поймешь, что Java-программист имел в виду, можно мутировать его или нельзя, поэтому ты уже на Котлин-стороне можешь в List присвоить или в MutableList. В обратную сторону работает из коробки, так как джавовский метод принимает менее типизированные вещи, туда можешь любую подходящую котлиновскую штуку передать. Соответственно, когда ты из Java что-то получаешь, ты должен более специфичный тип указывать и наоборот. А если ты будешь передавать не List, а String, то не разрешит.
Опять же, принимает, например, джавовский метод List, мы же не знаем, какой, поэтому можно туда и MutableList, и обычный List передать.

Надо заворачивать в какие-то врапперы котлиновские?
А когда используются джавовские либы какие-то либо, какой правильный подход?

Котлин задизайнен так, чтобы джавовские библиотеки было просто использовать. Не, ничего не надо заворачивать. Большинство Java библиотек просто работают с Котлином вообще без проблем. It just works. Но удобство зависит от дизайна библиотеки, конечно. А если там еще nullability-аннотации прописаны, то Котлин сразу видит, nullable или нет результат. Например, в джавовых либах, у которых такой же порядок аргументов, понятно, что последним аргументом принимается какой-нибудь предикат. У Котлина, например, специальный синтаксис, когда последнюю лямбду удобно передавать за круглыми скобочками. Берешь какую-нибудь JavaFx, и без каких-либо дополнительных адаптеров код на Котлине получается красивее, чем если бы ты JavaFx использовал на Java, удобнее, приятнее его смотреть. В Котлине ты можешь использовать его без всяких специальных адаптеров, просто можешь использовать красиво по-котлиновски. Но даже без них джавовские либы приятно использовать. Понятно, что ты можешь еще написать себе каких-то адаптеров, и это будет еще круче. Любая либа становится круче и удобнее, если ты просто начинаешь использовать её из Котлина, просто по факту использования Котлина.

У тебя аж голос изменился, так эмоционально всё описываешь.

Я просто видел это много раз, тебе просто так приятнее программировать. Конечно. Идея та же: Котлин приятнее как язык, и мы там пытаемся сделать максимально seamless интероп со всякий C-шной экосистемой, просто чтобы дать людям возможность использовать их существующие либы из более приятного языка, без каких-либо барьеров.
Мы на такое же надеемся и с Kotlin Native.

Кстати, а ведь при переходе на Native там не должен ли измениться смысл языковых конструкций?

Мы не ставим целью добиться «Write once, run anywhere», не стоит такой цели, чтобы было абсолютно идентично. Конечно, у нас, даже если посмотришь на Kotlin JavaScript, какие-то конструкции немного по-другому работают. Понятно, что ты можешь залезть в какие-то платформо-специфичные штуки, и их поведение будет соответствующим, но это нормально. Наша задача немного другая: мы хотим некое подмножество языка, вроде Common Kotlin или Portable Kotlin, на котором ты будешь писать, и оно будет работать отовсюду. И мы многие вещи специально не фиксим, чтоб сохранить перформанс. Если ты пишешь под одну платформу, и тебе всё равно, а под несколько платформ ты просто какие-то вещи будешь обходить стороной.

Задача, в первую очередь, не смотря на эту переносимость, дать нативной платформе высокопроизводительный код, а не писать еще одну виртуальную машину. На JVM у нас перформанс, как у Java, на JS у нас перформанс почти такой же, как у JS, на Native нативный перформанс. Эта попытка приводит к огромному перформанс-оверхеду. Многие пытаются транспилировать какую-нибудь джаву в тот же JS, попытка полностью эмулировать джаву. И на JVM-ном Котлине число 0 превратится в строку «0. Какие-нибудь банальные глупые вещи: ты берешь double, конвертируешь в String. Разное поведение. 0», а на нативном JS будет просто ноль. Пускай будет эта небольшая разница, зато нативный перформанс. Можно было бы на JS это пофиксить, но тогда все преобразования чисел в строки стали бы намного тормознее, потому что он будет обвешан дополнительными проверками — кому это надо? Таких примеров очень много, где мы специально принимаем решение сделать разное поведение из перформанс-соображений. У нас нет никакой своей специальной тормозной функции, которая преобразовывает числа в строки, у нас просто нативное JS-ное преобразование. Всё-таки семантика основных конструкций языка — классы, наследование, вызовы и так далее — все работает так же. Но там, где это не критично. Все тесты проходит.
У нас есть пример огромных проектов, как собственных, так и внешних, в которых написано много кода на Котлине, он компилируется и работает под JVM, JS, Native — и всё это нормально работает, даже несмотря на то, что где-то поведение немного отличается.

А корутины работают на всех платформах?

Это фича языка, которой всё равно, под какую платформу ты её запускаешь.
Да.

То есть вся размотка стека, вот это всё…

Нам ведь не нужна поддержка от платформы, вот в чем фишка. Да, это всё исключительно компиляторная фича. Мы не делаем это каким-то хаком в JVM. В отличие от проекта Loom и так далее. Это фича компилятора, поэтому мы можем то же самое сделать под любую платформу.

А если взять котлиновский код и попытаться его зареверсить, например, в Java?

Зато у тебя красивый код на входе. То ты увидишь там всю эту дичь, которую компилятор выдал. Это уже задача компилятора сделать из этого нечто, что будет работать. В смысле — зато ты написал красивый код. Например, пишешь for ( i in 0.. В Котлине много конструкций высокого уровня, которые потом превращаются в какую-то низкоуровневую пургу. Но писать приятно, так что какая разница, как оно там компилируется. 10 ), и это разворачивается в цикл for (int i = first и прочую дичь. Быстро, потому что разворачиваются в соответствующие нативные конструкции.
Оно работает.

А кто-нибудь сравнивал одни и те же программы на разных платформах?

У них разные задачи. Нет, и более того… несмотря на цель сохранить перформанс, платформы изначально несравнимы. Какой смысл сравнивать JVM и JS.

А какой смысл писать на сервере на Node.js?

Не ради перформанса. Так понятно зачем! Зачем изучать новый язык, новых программистов нанимать, если JS-программисты спокойно пишут бэкенд. Люди пишут для того, чтобы реюзать своих JS-программистов. Если ты умеешь программировать на Котлине, то один раз написал бизнес-логику и дальше пожалуйста — гонять ее на джавовом бэкенде, годняй на JS-фронтенде, гоняй в нативном микросервисе или чем-то — пофиг. Мы такую историю хотим дать с более хорошим, типизированным языком. В этом как раз цель Котлина.
Если код на Котлине, он сможет скомпилироваться куда угодно.

Ты в своих предыдущих проектах стал бы использовать Котлин, если бы он был изобретен сильно раньше?
У тебя были доклады вроде «миллиона котировок».

Всё, что я говорил в те времена, можно делать на Котлине. Конечно. Поэтому не использовать его на JVM большого смысла нет. На JVM это просто более удобный язык, чем Java, компилирующийся в тот же самый байткод. Или если ты делаешь библиотеку, которую должны использовать клиенты, которые Котлин не могут использовать. Разве что у тебя legacy, enterprise, и это просто запрещено. Байткод тот же самый, а на входе — не только более удобный, но и более компактный язык. А если ты пишешь для себя, нет никакого смысла не писать на котлине под JVM. Но не заставляет писать совсем жестко типизированную дичь, — как всегда в JVM можно сказать компилятору «я знаю лучше тебя». Он еще и более типизированный, защищает от большего количества ошибок. Но в обычной практике API на Котлине получаются более документированными, более строгими и более безопасными. Механизмы обойти компилятор у тебя есть. Его читать проще, меньше воды в коде. Код получается надежней, реже падает по исключениям. С таким кодом приятней работать, в нем меньше воды и больше сути, которую ты хотел выразить.
Многие штуки, когда в Джаве пришлось бы писать бойлерплейты, в Котлине пишется в одну или несколько строчек.

Об этом фестивале мы совсем недавно писали на Хабре. В эти выходные Роман будет на фестивале TechTrain с докладом «Зачем нужен еще один язык программирования?». Загляните, вдруг понравится.

Раз уж ты начал говорить про JS и обучение, насколько сложно на Kotlin переучиться с Java?

У нас есть и книжка «Kotlin in Action», и сайт, ориентированный на Java-программистов. Вот с Java как раз очень легко и вообще никаких проблем. И это не случайно получилось, это «by design». По опыту, Java-программисту нужно потратить от двух дней до двух недель, и всё, вышел из тебя отличный Kotlin-программист. Велосипед не изобретали. Изначально в дизайне Котлина заложено, что Java-программистам должно быть легко на него перейти. Есть слайд о том, откуда родились разные языковые конструкции. У Андрея Бреслава на прошлом JPoint есть хороший доклад, откуда что Kotlin позаимствовал. Оно и называется как в Java, чтобы было проще, чтобы не нужно было изучать что-то сильно новое. Видно, что 60-70% взялись из Java. Это большие языки типа Java могут себе так позволить.

Никогда ничего не берется as-is. У них вообще есть такая дизайн-цель — об этом еще Гослинг говорил, что если мы что берем, то обязательно называем по-другому. В Kotlin же, если ты уже знаешь, что такое «класс», это и должно называться «классом». Мы люди большие, можем себе позволить, пускай люди учат. Люди знают цикл while — нет смысла его переименовывать. Если знаешь, что такое «интерфейс» — должно называться интерфейсом. Но зачем? Хотя можно найти 100500 более хороших названий для него. Даже я делал какие-то доклады, «Введение в Kotlin», и все это расчитано на них, нужно рассказать только какие основные вещи в Котлине новые и интересные.
Кроме того, большинство наших обучающих материалов рассчитано на Java-программистов.

У нас пока недостаточно обучающего материала. Проблема возникает, если ты с нуля учишься программировать. В перспективе, на сайте тоже будет.
Сейчас идет работа над книжкой, которая выйдет в этом или следующем году, и она будет специально для новичков.

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

Еще стараемся сотрудничать с вузами. Да, для совсем новичков. Android-программисты тоже — больше половины, вроде бы по последним данным. На пути переучивания Java-программистов мы очень далеко продвинулись. Миллион программистов как минимум уже переучились, и никаких проблем нет. И все переучились без проблем. А вот на обучении с нуля — мы находимся на очень раннем этапе пути.

А есть какие-то шутки, которые людям сложно понимать с нуля?

Людям сложно понимать вложенные циклы, рекурсию, референсы. Они такие же, как в других языках. Обучение человека с нуля — это некое искусство, там есть сложные моменты, которые надо адекватно объяснять. Это известная тема, неважно, на каком языке ты программируешь. Например, мы получаем фидбэк от университетов. Но в этом плане Kotlin очень хорош. Обычно учат простым процедурным вещам: как писать циклы, как писать функции вызывать и так далее. Если взять базовый курс программирования в каком-нибудь ВУЗе и посмотреть, чему их учат, окажется, что никто не учит сразу классам. Но не каждый язык одинаково хорош. Раньше многие учили на C++, потом ринулись в Java, теперь на Python. Чтобы написать простой хэлловорлд, нужно написать класс, puiblic static void main… А ты же учишь не объектно-ориентированному, а процедурному программированию. Та же Java была когда-то очень популярна, и до сих пор многие вводные курсы читают на Java, но это не самый лучший язык именно для вводного курса программирования. Зачем новичку парить мозг? В Java, чтобы что угодно написать, надо объявлять класс.

По сравнению с C++ можно поспорить, потому что C++ очень большой язык. Kotlin в этом плане больше подходит для обучения: открываешь файл, пишешь функции свои, почти как в Python, только с типами. Но когда учат на плюсах, то учат очень ограниченному подмножеству. Это не значит, что на плюсах нельзя учить программистов, можно. А Kotlin — хороший типизированный язык. Рассказывают небольшие фишки, очень аккуратно, чтобы обучающийся не сделал шаг влево и шаг вправо. Я свою дочку в качестве первого языка программирования научил Питону. Если хочешь хорошему нетипизированному языку научить — это Python. Когда ты вообще не умеешь программировать, тебе нужно очень много узнать. Чтобы не забивать ей голову типами сразу. Нужно постепенно учить. Не бывает так, чтобы ты сразу всё узнал. Следующий же язык должен быть типизированным, чтобы разобраться с типами.
Поэтому проще вначале научить всяким императивным конструкциям — циклы, ввод, вывод, функции, процедуры — а типы отложить пока в сторонку.

То есть ты считаешь, что типизация — полезная штука?

Это must have. Я не просто считаю, что она полезная. Поэтому без типов никакая разработка не масштабируется. Документацию никто не пишет в своем коде, а даже если пишет — не мантейнит и не читает. Десять тысяч — уже тяжело. В одиночку еще можно программировать на нетипизированном языке, программки до десяти тысяч строчек от силы. А если у меня большой проект, много разработчиков, это просто неподдерживаемо, ничего не понять.

А как джаваскриптеры живут?

Так и живут. Ну как живут… подевелопили проект, бросили, пошли к следующему. Сделал — и всё. Большинство JS-проектов — очень маленькие. И если кто-то пытается делать большой проект на JS, скорей всего переходит на type checkers, на Flow или TypeScript. Есть большие проекты на JS, но там не живут, а страдают. То же самое с Python. Что-то большое поддерживать на JS сложно именно по причине отсуствия типов. Ты можешь на Django зафигачить огромный проект, если ты сильно не изобретаешь велосипед. Пока это какие-то шаблонные DSLки, всё отлично. Когда я говорил про сложность проекта в строчках, это, конечно же, обман. Пишешь джанговские классики, пользуешься обычными механизмами. Я могу иметь огромнейший сайт на Django, где огромное количество строчек кода, десять тысяч формочек и CRUD-страниц. Сложность не определяется строчками кода. Но как только я начну писать сложную бизнес-логику, делать какую-то иерархию классов, моделировать сложный домен, то в нетипизированном языке я очень быстро умру. Но так как все они одинаково нафигачены, бизнес-логики нет — это всё легко и понятно. И всё это поддерживать будет невозможно. Очень быстро. Это core belief в команде Kotlin, что язык промышленного масштаба должен быть строго типизированным.
На этом строится философия Kotlin, он еще более типизированный, чем Java, еще более строгий.

Правильно понимаю, что если есть какой-то большой сложный фронтенд, то даже там Kotlin уже имеет смысл?

Если фронтенд большой и сложный, значит, в нем вряд ли просто какие-то CRUD-странички. Конечно! А вот если это большое сложное веб-приложение, в нем много сложной логики — конечно, нужен типизированный язык. Опять же нужно различать: если у тебя большой и сложный сайт в котором много страниц, и все страницы одинаковые, и логики там нет — вьюшки, то это одно. Это объясняет, почему TypeScript и Flow набирают популярность — это типизированные штуки поверх JS.

Если ты будешь сравнивать разработку веба на TS и Kotlin/JS, естественно, TS победит, потому что он заточен под JS-экосистему, он прямо для нее создан. Про Kotlin есть еще момент, что Kotlin для JS не может победить TypeScript просто так, один на один. Можешь и на фронтенд скомпилировать, и на бэкенд.
Зато Kotlin/JS победит, если тебе надо этот код шарить.

И там будут всякие заточки, о которых ты говорил — вроде конвертирования double…

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

Когда-то ты был связан с олимпиадным программированием.

Я до сих пор связан.

А чем ты занимаешься?

Провожу олимпиады 🙂 И сам участвую, но редко.

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

Она появилась лет, наверное, пятнадцать назад. Сейчас она доступна практически всегда. Java и C++ — это два стандартных языка, которые все поддерживают, а дальше — вариации, в зависимости от соревнования.

А на Java сложней выиграть, есть какие-то скрытые оверхеды?

В нормальном соревновании — одинаково, если в нем задачи больше на идею и правильный алгоритм. Зависит от соревнования. Плюс бывает очень маленькое время выполнения теста. Но бывает какая-нибудь дичь, когда задачи подразумевают неасимптотическую оптимизацию, где надо всё до такта оптимизировать — там, конечно, на Java будет тяжело, придется много стараться. А если у тебя лимит на все — секунда, то на Java ты можешь проиграть просто за счет того, что пока HotSpot разогревается и компилируется — уже секунда прошла. Грубо говоря, если у тебя ограничение по времени исполнения несколько секунд, то HotSpot за секунду прогревается на небольшом коде и пофиг.

Но нормальные соревнования (популярные, поддерживаемые хорошими людьми) — там стараются сделат задачи и окружение так, чтобы на Java и на плюсах были одинаковые шансы. Да, бывают дикие соревнования, где на Java тяжело. Где-то некоторые вузы отказались учить Java и перешли на Python — и из-за этого, в том числе, сейчас многие соревнования научились Python. И причины понятны: хоть Java и не растет в образовании, но и сильно никуда не убывает. Соревнования, в основном, студенческие. Это такой стабильный третий язык из поддерживаемых. В школьные и студенческие годы люди будут постоянно выступать и тренироваться. Есть и профессиональные соревнования, и большие компании делают что-то вроде Facebook Hacker Cup, где может каждый участвовать, но всё равно, основная тема в спортивном программировании — школьная и студенческая. Поэтому выбор языков определяется тем, что используют в образовании. Но после выпуска из ВУЗа, после выхода на работу — очень мало людей будут продолжать участвовать. Для многих программистов Java — первый язык, соответственно, все соревнования стараются поддерживать Java. Если учат плюсам, яве и питону, то и на соревнованиях будут они. Он для системного программирования, низкоуровневого программирования, тебе не нужно иметь миллион C++-программистов, это бессмысленно совершенно.
Ради соревнований учить С++ — дичь.

А как тебе идея — добавить Kotlin в список стандартных языков?

Есть ICPC, который ежегодно проходит, собирает сотни тысяч участников по всему миру, больше сотни команд проходит в финал. Ну вот, собственно, эту идею мы активно и продвигаем. Сейчас там список языков такой: C/C++, Java, Python и Kotlin. В ICPC Kotlin поддерживается. На студенческих соревнованиях используются те языки, которым студентов учат.
Но пока, естественно, на нем никто особо не пишет, по причине вот какой проблемы: проникновение в образование еще на очень раннем этапе.

А где-нибудь уже учат Kotlin?

Например, в Питерском Политехе. Где-то точно учат. Но мы пока на очень раннем этапе, на «шаге 0» этого процесса.

Там нет каких-нибудь фатальных недостатков?

Просто образование — консервативное. Нет, для начального образования Kotlin лучше, чем остальные языки. Никто не любит изменений. У людей есть готовая программа, учебники. Это может раз в десять лет пересматриваться.
Зачем профессор, который учит на первом курсе студентов программированию, будет менять язык, в чем бонус?

Бонус, например, в том, что человек, который оттуда выйдет, будет более приспособлен к действительности.

Потому что не так важно, какой язык ты изучил первым. Нет. Плюс всё это постоянно меняется. Профессиональный программист за свою жизнь изучает десяток языков и использует около трех языков активно. Важно, какой ты багаж языков имеешь по выпуску из вуза — это другая тема, это важно. Чему тебя научат программировать первым — не так важно. Например, в Китае существует проблема, которая выясняется после общения с ребятами оттуда. И здесь мы сталкиваемся с проблемами на консервативных рынках, которые ориентированы на авторитет. А потому что вот, не учили ребят Котлину в ВУЗе, и они ничего нового не хотят изучать, а зачем им?
Берешь какую-нибудь крупную контору, в которой много программистов, спрашиваешь — почему вы не используете Котлин?

А у нас не так?

В разных культурах по-разному. Это всюду так, просто в разном масштабе. Где-то люди более самостоятельные, больше склонны к экспериментированию, инновациям. Есть культуры, в которых как гуру сказал, или как учитель сказал — так и будешь делать. Где-то пальцем не пошевелят и будут делать ровно то, чему их научили. Где-то люди пойдут и сами все изучат. В России внедрений Kotlin больше, но это и потому еще, что мы изначально отсюда, больше на конференциях выступаем и так далее.

Я вырос, когда программировали те, кому это нравилось, они всё изучали самостоятельно, потому что ничего не было. Это в моем поколении программисты были энтузиастами. Возьми современного программиста, большая часть делает это не потому что любит, а потому что этому научили и теперь платят много денег. А сейчас это массовая штука, которой учат. Зачем им?
Соответственно, такие люди не будут изучать технологию, которая только что вышла.

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

На Котлине ты, скорее, получишь больше удовольствия.
Нет, конечно!

Есть конкретные штуки, которые реально имеют бизнесовое значение — мы же говорили о переиспользовании между фронтом и бэком…

С другой стороны, и удовольствие тоже. Не всем это нужно. Им платят деньги — они работают, какая им разница, получают они удовольствие или нет. Не все от своей работы вообще получают удовольствие. Закончился рабочий день — закрыли и забыли об этом, и начали заниматься другими своими вещами.

Это как-то очень уныло, если не ужасно.

Какой бы ужасной она ни была. Это правда жизни, к сожалению. Kotlin, не Kotlin.
И таким людям, конечно, все равно.

Насколько понимаю, в JetBrains как раз очень многие работают потому, что им нравится работать.

Специально отобранные люди, мотивированные, которым действительно нравится вот это дело.
JetBrains в этом плане — нерепрезентативная выборка, естественно.

Какое-нибудь напутствие, какое-нибудь откровение?
Наше время потихоньку подходит к концу, поэтому такой вопрос: можешь ли ты передать что-нибудь нашим читателям на Хабре?

Единственный вывод, который можно сделать из нашего разговора: от работы счастлив тот, кто получает удовольствие. Могу передать пламенный привет 🙂 А откровения никакого не скажу, какое может быть откровение? А потом по каким-то причинам им стало любопытно, жизнь заставила, они попробовали Kotlin, и неожиданно для себя открыли, что от работы можно получать удовольствие. Я читал несколько блогов хороших людей, которые программировали на Джаве, просто работали, не получая никакого удовольствия. Что можно любить язык программирования. Что можно любить то, что ты делаешь. Конечно, язык — это некий инструмент, но можно относиться к нему опосредованно, а можно его любить. А не просто использовать, безэмоционально, как некий инструмент. Это разное отношение, в том числе разное отношение к работе создает.

Может даже, не только после Java. К Kotlin очень много людей испытывают теплые чувства, сравнимые с любовью, именно потому, что на Kotlin просто приятно программировать, особенно после Java. Есть языки с большей функциональностью, с более сильными фичами, есть языки с более строгой системой типов, есть языки, где все pure, есть, где всё наоборот — unsafe. Наверное, нет языков, на которых настолько приятно (именно такое слово) программировать. Но в Kotlin такой баланс, что неспроста он на StackOverflow в опросе этого года оказался вторым в топе most loved languages. Возьми любое измерение, и найдешь языки, которые в этом свойстве круче Kotlin. Но Rust нам не конкурент, потому что Rust — язык системного программирования. Первым, кажется, стал Rust. Нисколько не обидно, что Rust в этом плане обогнал Kotlin. Мы в эту нишу не лезем. Некоторых фичей Rust у нас нет и никогда не будет, потому что они просто не нужны прикладному программисту. Мы боремся, чтобы Kotlin стал основным языком для прикладного программирования, на котором приятно решать прикладные задачи. Он должен свой домен трансформировать в код. Не должен он вручную управлять памятью или думать о тонкостях владения, прикладной программист должен решать бизнес-задачи. Мы пытаемся эти мешающие факторы устранить. И это должно быть максимально прямое преобразование без каких-либо мешающих ему факторов. Чтобы ты свою бизнес-задачу максимально прямо, без воды и лишнего кода преобразовывал в решение.

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

Как и любой современный язык. Естественно, Kotlin учитывает опыт предшественников. Неспроста же в Kotlin сделаны nullable-типы. Это и есть прогресс — когда что-то новое создается с учетом старых недостатков. Это известный факт, и если ты делаешь новый язык — нужно ее решать. Ну что далеко ходить, возьми любой энтерпрайз, пойти в любую крупную контору, посмотри их крэш-логи, и увидишь, что самый частый exception — NullPointerException. И так далее. Поэтому мы очень много внимания в языке уделяем nullability. Почему его любят? Если ты дизайнишь язык не абстрактно, не как академическое упражнение, а пытаешься решить проблемы людей, с которыми они сталкиваются часто, то язык получается хорошим. Потому что он решает их проблемы.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть