EaxCast S02E10 — интервью с Евгением Бурмако о Scala, макросах и академической карьере

21 декабря 2014

Темы выпуска: Как оказаться в EPFL, Scala, её система макросов и как сложно бывает порой смешивать макросы и систему типов. Предыдущие выпуски: шестнадцатый, пятнадцатый, четырнадцатый.

Слушать онлайн:
http://eaxcast.podfm.ru/_eaxcast/17/

Скачать файл:
http://eaxcast.podfm.ru/_eaxcast/17/file/podfm_eaxcast__eaxcast_210.mp3

Шоу нотес:

Голоса выпуска: Евгений @xeno_by Бурмако, Валерий @sum3rman Мелешкин

 

Валерий: Всем привет! С вами EaxCast. Я, Валерий @sumerman Мелешкин, и в гостях у нас сегодня Евгений @xeno_by Бурмако. Наш гость занимается разработкой макросов для Скалы. Я не настоящий сварщик, я Скалу знаю немного, на ней не писал, так что если буду задавать какие-то странные с вашей или с твоей, Жень, точки зрения вопросы, прошу в меня не кидать помидорками. Я постарался подготовиться к выпуску, но я могу чего-то не знать или не понимать, потому что я не скалист. Жень, расскажи в двух словах о себе. Как так получилось, что ты занялся скала-макросами, и чем ты занимался до этого?

Евгений: Окей. Всем привет. Как меня Валера уже представил, я занимаюсь Скалой вообще и в целом, и большей частью макросами, и начал это делать примерно 3 года назад, когда приехал учиться в аспирантуре Федеральной Политехнической Школы Лозанны, которую я дальше буду называть просто EPFL по английской аббревиатуре. До этого некоторое время – лет 5 или 6 – я кодил на C# всякий аутсорсный софт и в процессе последнюю пару лет занимался интересными проектами, связанными с мета-программированием. Что-то меня в нем привлекало, я, правда, не знал, что именно к мета-программированию у меня лежит душа, но потом счастливый случай, о котором мы поговорим позже, позволил мне определиться. Итак, что конкретно привело меня в EPFL – это проект, который назывался Conflux он даже где-то опубликован сейчас на гугл-коде, и я даже пообещал себе портировать его на GitHub, но не суть важно… Итак, смысл конфлакса заключался в том, что пользователь может написать программы на C#, которые работают с числами, используют какие-то библиотеки, соответствующе написанные, и потом такие программы или алгоритмы транслируются в CUDA, то есть дальше могут исполняться на графическом процессоре.

Это все было довольно интересно – до того момента, пока я не увидел публикацию ребят из EPFL, в которой они описывали примерно то же самое, но на Скале. Это открыло мне глаза, и я подумал: «О да! Они все понимают! Надо идти знакомиться». И в результате этого знакомства я написал пару писем Мартину Одерски, создателю Scala, который теперь уже мой профессор, и он предложил мне приехать. Было несколько вариантов, в какой роли приехать, и, немного подумав, я выбрал вариант аспиранта. Подал документы в EPFL, и, после некоторого ожидания, мне прислали приглашение поехать в Лозанну. Итак, когда я приехал в Лозанну, в сентябре 11-го года, как раз примерно три года назад, мне было совершенно непонятно, чем заняться. И тут меня осенило: «А почему бы не сделать макросы для Скалы?» Наверное, это прозвучало глупо, но примерно так оно и было. Сидел я и читал разные научные публикации, чтобы хоть как-то определиться с тем, что нужно делать, и каким-то образом я наткнулся на публикации про Лисп, и тут понял, что на самом деле макросы меня очень давно интересуют. Я уже упоминал, что работал в большинстве своем на C#, и у нас была такая прекрасная система, язык, который и сейчас есть и прекрасно развивается, — Nemerle. У Nemerle основной фишкой были, насколько я понимаю, именно макросы, и они запали мне в душу, после чего благодаря чтению публикаций про Лисп и родился проект про скало-макросы. Я думаю, такого введения нам хватит. Если какие-то конкретные вещи интересны, буду рад рассказать подробнее.

В.: Я правильно понимаю, что ты попал в EPFL уже не будучи студентов или аспирантом, то есть уже пошел работать в индустрию, сколько-то отработал, у тебя случился перерыв после окончания университета, и ты после этого все равно поступил аспирантом в EPFL?

Е.: Да, примерно так и было. Я начал работать на втором курсе. Так довольно часто случается на наших факультетах, и когда появляется возможность работать, мало кто отказывается. После окончания университета я должен был отработать по распределению. Есть такое понятие у нас в Беларуси, когда студент бесплатного отделения должен два года проработать на какую-нибудь контору, которая как-то связана с государством. После этих двух лет можно заниматься чем угодно, чем я и не преминул воспользоваться.

В.: То есть в EPFL довольно легко поступить в аспирантуру, даже если прошло несколько лет с тех пор, как ты что-нибудь закончил?

Е.: Хороший вопрос. Мне сложно говорить что-то насчет поступления. Если честно, я до сего момента не знаю, почему именно меня взяли. Дело в том, что процедура поступления непрозрачна. Ты просто собираешь кучу документов, привозишь свои аттестаты, справки из университета, выписки из экзаменационных ведомостей и так далее и отсылаешь их. Так сказать, спортлото. А потом спортлото тебе отвечает «да» или «нет». Было бы очень интересно, как это на самом деле обстоит, но для меня это пока что загадка. Насколько я понимаю, для них не настолько важно, какая академическая степень есть у соискателя, потому что у меня есть только степень бакалавра, то есть я не занимался магистратурой, поэтому сложно сказать.

В.: Ок, понятно. Я так понимаю, что ты довольно тесно работаешь со Scala Team. Расскажи, как этот процесс работы устроен, как вообще Scala Team работает, являешься ли ты частью Scala Team, или ты просто с ними работаешь?

Е.: Ок, хорошо. Во-первых, есть большой вопрос: что такое Scala Team? Я пока приму определение: Scala Team – это те, кто могут коммитить в scala/scala то есть те, у кого в соответствующей организации на GitHub (GitHub.com/scala) есть права мержить пулл-реквесты. Немного из истории Scala (я думаю, это здесь будет уместно). Изначально Scala был создан EPFL, то есть это был исключительно академический проект, который начал Мартин Одерски с такими же аспирантами, как и мы сейчас с ним работаем, и потом, по прошествии времени, благодаря усилиям разработчиков, благодаря интересу со стороны коммьюнити, Scala превратилась во что-то, что используется в реальных условиях. И, соответственно, понятие Scala Team немного размылось.

В начале это был попросту Мартин и его студенты, которые что-то делали для своих исследований и попутно чекинили коммиты, у нас тогда еще и SVN был, но позже контроль за репозиторием scala/scala перешел в руки TypeSafe – коммерческой компании, которая была создана Мартином для того, чтобы осуществлять поддержку языка, чинить баги, делать релизы и так далее. Поэтому появился TypeSafe Scala Team – люди, которые занимаются Scala в пределах TypeSafe. В целом, для наших целей определения выше (т.е. люди, которые могут коммитить в наш репозиторий) будет достаточно. Итак, по этому определению я являюсь частью Scala Team, ну еще некоторое количество коммитов сделал в репозиторий scala/scala, поэтому это совсем уж справедливо, и могу сказать, что процесс нашей работы построен примерно следующим образом. Раньше Scala Team проводил достаточно долгое время в разработке новых фишек. Учитывая академическую историю Scala, это вполне естественно. Логично, что в академической среде люди занимаются всякими прикольными исследованиями, и им интересно что-то по-быстренькому сделать, потом, может, что-то подпилить и написать какую-то публикацию. Поэтому раньше фокус был на разработке новых фишек. Все это было весело и интересно.

А потом, в связи с появлением TypeSafe, дела замедлились и стабилизировались, потому что наши корпоративные (можно сказать более обобщенно, «большие») пользователи хотят спокойной жизни. Поэтому сейчас большая часть работы заключается в фиксе багов. В чем это выражается? Скажем, кто-то занимается какой-то конкретной областью, например, я делал макросы и рефлекшн, и если вдруг появляется какой-то баг в нашем трекере, которым сейчас является JIRA, то человек садится в свободное время и что-то делает. Но это касается людей из EPFL. Мне сложно сказать, как устроено на стороне TypeSafe, никогда там не работал, но могу предположить, что у них есть ряд контрактов с определенными пользователями Scala, и если этим пользователям интересны какие-то специфические проблемы Scala, то народ садится и фиксит. Вкратце резюмируя, раньше мы разрабатывали очень много новых фич, а сейчас по большей степени идет maintenance.

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

Е.: Три года и пару месяцев.

В.: Тебе больше нравится академическая карьера, или ты больше хотел бы вернуться в индустрию?

Е.: Это очень хороший вопрос. Понятно, его часто задают аспирантам. Такой экзистенциальный вопрос, который часто задается людям, которые пишут диссертации. Что я могу сказать? Так уж сложилось, что с того момента, как я начал делать макросы, моя работа заключалась в том, чтобы я делал какую-нибудь такую штуку, которую пользователи могли по-быстрому использовать, что было бы полезно и позволяло бы добиться каких-нибудь вещей, которые раньше были невозможно. Потом этот виток порождал новый виток фидбека, в результате чего мы понимали, в какую сторону нам двигаться дальше, снова выкатывали какую-нибудь новую функциональность, которой пользовались и так далее. Как ты видишь из этого описания, времени на сочинение научных публикаций особенно не оставалось, поэтому так сложилось, что фокус моей аспирантуры был направлен на практику. Я думаю, душа у меня больше лежит к индустрии, хотя есть еще пара лет, которые мне остались до окончания и написания диссертации. Есть еще пара лет передумать.

В.: Но ты планируешь написать диссертацию?

Е.: О да, это конечно. Уже очень много было сделано по макросам, и я считаю, фактически своим долгом задокументировать все это для того, чтобы наш опыт был доступен последующим поколениям. Было немало ошибок, прикольных озарений.

В.: Понятно. У меня теперь такой вопрос странный. Понятно, что ты долго работаешь со Scala, понятно, что, наверное, ты пошел в Scala Team, потому что тебе нравится этот язык. Ты готов отругать Скалу? Сказать, что такого в ней отвратительного, что бесит каждый день? Если это есть.

Е.: Хорошо, понятно. Есть у меня один друг, который бы с удовольствием этим занялся. Но я здесь буду гораздо более спокойным. Наверное, самая большая проблема, которую я вижу для себя как для пользователя Scala, — в конце концов, компилятор Scala мы пишем на Скале, а также все другие вспомогательные проекты, которыми я сейчас занимаюсь вроде scala-meta, поэтому каждый день очень часто приходится пользоваться нашим компилятором. И его главный минус, я считаю, — это скорость. Понятное дело, можно рационализировать эту ситуацию сколько угодно. То, что тайпчекер должен делать большое количество работы, потому что в языке есть много интересной функциональности, есть даже implicit’ы, например. Но факт остается фактом – компилятор медленный. Я думаю, в новом экспериментальном компиляторе dotty, которым сейчас занимается Мартин, мой профессор, вместе с некоторыми членами нашей лаборатории, ситуация улучшится, но пока сложно что-то утверждать. Поживем – увидим. Еще вот есть одна штука, которая беспокоит некоторых наших людей из команды, в том числе, Адриана (есть такой чел, Адриан Морз, который сейчас является тим-лидом скала-команды в TypeSafe)… Ему хотелось бы поэкспериментировать с заменой имплиситов на что-нибудь более прямолинейное и более предсказуемое, чем для вычисления на типах. Я немножко поясню для тех, кто не знает Scala. Одной из очень интересных возможностей продвинутых систем-типов (как, например, в Скале или Хаскале) является способность производить вычисления типами времени компиляции.

Скажем, какой-нибудь класс можно при помощи этих вычислений на типах разобрать на список имен полей и список типов полей. Зачем это нужно? Ну например, для того чтобы создать какой-нибудь сериализатор во время компиляции абсолютно без всякого рефлекшена. Как это примерно происходит? Если интересны детали, можно почитать документацию проекта, который называется shapeless. Скажем, нам нужно получить сериализатор для какого-то конкретного типа. При помощи магии имплиситов (в детали я вдаваться не буду) можно у этого типа узнать список всех полей, список типов всех полей и даже их имена. Имея эту информацию, можно на уровне типов буквально сгенерировать нужный код, который, скажем,в json сохраняет все те поля, создавая специальные метки, сохраняет значение этих типов полей под ключами, которые соответствуют именам. Что самое прикольное, на что хочу акцентировать внимание, — эти вещи происходят исключительно во время компиляции. Соответственно, если произойдет какая-нибудь ошибка, то о ней мы узнаем во время компиляции, а не во время того, как программа будет запущена и кидает какой-нибудь reflection-exception. Это, несомненно, плюс.

Что в этом механизме не очень – это его экстравагантность. Дело в том, что вычисления на типах, как в Скале, так и в других языках (где есть похожие механизмы), производятся не совсем на Скале. Они записываются на Скале, но для выражения очень простых идиом часто приходится городить большой огород. Еще один пример – шаблоны C++. Вроде бы шаблоны С++ тьюринг-полны, но воспользоваться этой полнотой не так уж и просто и удобно. В этом плане Скалу можно улучшить, придумав какой-нибудь язык или подмножество языка Скалы и сделать его доступным во время компиляции. Таким образом, если мы хотим выполнить какую-либо операцию над типами, которая является аналогом IF-а, давайте мы запишем просто IF, а не будем создавать несколько имплиситов, между которыми будем диспетчеризоваться. Да, очень хорошо у академических людей спрашивать невинные вопросы, можно залезть в большие дебри. Просто чтобы остановиться, скажу небольшое резюме. Во-первых, это время компиляции, и, во-вторых, было бы неплохо иметь какой-то хороший способ записи вычислений над типами. Здесь, конечно, нам могут помочь макросы, но тут я пока что остановлюсь.

В.: А вот ты заговорил о вычислениях типов с использованием имплиситов и упомянул шаблонную «магию» C++. Я, как человек, немного знакомый с шаблонной «магией» С++, заинтересовался. Я так понимаю, что IF моделируется выбором более специализированного имплисита. А каким образом моделируется цикл?

Е.: Ок, хорошо. Это хороший вопрос. Немного поясню. Наверное, в начале сделаем небольшой экскурс в то, как работают имплиситы, имплисит-параметры. Есть еще второй важный аспект имплиситов – это имплисит-конверсии, но здесь, я думаю, они нам будут не нужны. Кстати, интересный факт про имплиситы. Я считаю их, наверное, самой высшей фичей Скалы. Дело в том, что имплисит-параметры настолько элегантно позволяют моделировать очень много концепций, как, например, вычисление на типах или классы типов, то есть тайп-классы, что, я думаю, их значение сложно переоценить. Однако очень многие люди, которые только познакомились со Скалой или просто как-то понаслышке что-то схватили, в основном думают об имплиситах как об имплисит-конверсиях. Это такая функциональность языка, которая позволяет неявно (абсолютно незаметно для пользователя) конвертировать одни типы в другие. Ну и, конечно, если эту возможность использовать не по назначению, то есть очень сильно, то можно в этой библиотеке неплохо заплутать. Потому люди, которые узнали про имплисит-конверсии и думают, что эта функциональность действительно опасна, и они лишают себя удовольствия знакомства с имплисит-параметрами, которые являются шедевром, на мой взгляд. Итак…

В.: Извини, я тебя перебью. Я сам задал тему, а сейчас от нее отклоняюсь, но просто ты поднял вопрос, который меня сильно волнует когда я пытаюсь обсуждать Скалу, и опустим даже тему имплисит-конверсиями… Понятно, что можно любую фичу любого языка абъюзить. Но с имплисит-параметрами меня всегда волнует такой вопрос. Вот есть какая-нибудь Akka или любая другая крупная библиотека на Скале. Вот если у меня в коде какое-нибудь неожиданное поведение, я начинаю по коду копаться и пытаться понять, что происходит. И в этот момент у меня случается такое количество имплиситов, которые бы передавались оттуда в каждый уровень вложенности какого-то вызова, и не всегда очевидно, какой имплисит приехал, потому что я же не компилятор. Я не могу взять и в уме вывезти, чтобы понять, какой имплисит приехал. Если это execution context, его еще сравнительно просто зацепить глазами. Если это что-то, что приехало вместе с импортом пакета, то очень тяжело понять, что же туда приехало.

Е.: Да, могу сказать, что есть, действительно, такая проблема. Тут сложно давать какие-то общие рекомендации. Я думаю, автор каждой библиотеки должен для себя определять меру. Знать меру. В идеале хорошо бы, чтобы пользователи вообще не знали, какие имплиситы генерируются. Вот как, например, если мы используем тайп-классы, и при помощи какой-то магии на имплиситах или макросов мы каким-то образом синтезируем инстансы этих тайп-классов этих типов, было бы идеально, чтобы это работало каким-то магическим образом, и пользователю никогда не надо было бы туда смотреть.

В.: Нет, ну ты же понимаешь, что если какая-то библиотека попадает в продакшн, то пользователю сразу необходимо туда смотреть. Ну просто потому, что у него в продакшне что-то сломалось. Это вполне могла быть именно эта библиотека, почему нет? И если пользователь пытается разобраться в коде, а там там имплиситная магия (или если си ++, то шаблонная магия), то это резко хуже поддается понимаю пользователя, и нужен какой-то продвинутый разработчик, чтобы разобраться, что там происходит.

Е.: Да, поэтому я и начал говорить о том, что неплохо было бы, чтобы был идеальный случай. В случае же, когда разобраться тяжело, тут даже сложно что-то посоветовать. Понятно, что тем, кто знаком с компилятором, будет попроще, потому что есть соответствующие флаги, которые заставят компилятор пищать какую-то информацию про поиски имплиситов. Тем, кто использует IDE, будет тоже, думаю попроще, потому что… (хороший вопрос, где это было, когда я последний раз смотрел?) По-моему, и Scala IDE (Eclipse плагин) и плагин для IntelliJ IDEA, которые умеют подсвечивать места, где появляются имплиситы. Но я не знаю, умеют ли они показывать, какие имплиситы были выведены. Поправь меня, если я ошибаюсь.

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

Е. Понятно. Что могу сказать, чтобы подвести итог? Действительно, есть проблема, и нужно подходить к вопросу имплиситов ответственно. С эволюцией языка, мне кажется, культура нашего коммьюнити улучшается. Если некоторое время назад, когда меня еще не было (и я не могу говорить по личному опыту, но читал посты), людям очень сильно нравились вычисления на типах и имплисит-конверсии, и они стремились почаще и побольше использовать эту функциональность. А потом со временем выработались какие-то best practices на эту тему, и сейчас уже очень многие думают о том, будет ли пользователем эту удобно и прозрачно. Понятно, что худший случай звучит ужасно: какие-то неведомые элементы программы неводомо-откуда появляются, но на практике часто случается, что все довольно просто и изящно.

В.: Спасибо за комментарий. Вернемся к вычислениям на системе типов и имплиситам. Вопрос был: как смоделировать на имплиситах цикл?

Е. Ок. Я говорил, что шаблоны в С++ тьюринг-полны, и имплиситы тоже тьюринг-полны. По легенде, разработчики Скалы осознали это только постфактум, после того, как новая функциональность была внедрена в Скалу, народ понял, что имплиситы тьюриг-полны, все были очень довольны, наколбасили какие-то прикольные хаки. Как бы моделируем рекурсию? Есть у нас имплисит-параметры, т.е. параметры методов, которые выводятся компилятором автоматически, на основании того, что есть у нас в лексической области видимости, также есть функции, которые могут принимать несколько параметров и могут сами быть имплиситами. Предположим, что у нас есть корневая функция, например serialize, которая берет какой-то объект, который нужно сериализовать, и которая требует имплисит-параметр собственно сериализатор. Итак, у нас в лексической области видимости есть какие-то соответствующие имплиситы, которые скорее всего пришли из библиотеки. И чтоб будет делать комплилятор, если имплисит-параметр сериализатор если пользователь его явно руками не передал. Скорее всего, в библиотеке будет какой-то имплисит-метод, который принимает другой имплисит, который вычисляет список полей соответствующего класса плюс типы этих полей. Дальше этот метод, кроме первого имплисита, будет принимать еще ряд имплиситов, которые соответствуют каждому из элементов этого списка полей. Таким образом, рекурсия на имплиситах моделируется путем добавления имплисит-параметров на имплисит-методах. Таким образом, у нас есть метод, который сам по себе принимает имплисит-параметр, и он тоже будет проходить через имплисит-поиск, который реализован в компиляторе. Этот поиск может найти наш текущий метод, для этого метода снова понадобится имплисит-параметр, и так далее — пока мы не дойдем до «листьев» этого «дерева» поиска.

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

Е. Да, полное название – scala-meta. Расскажу немного…

В.: Извини. Я пытался понять отличие рефлекшна от мета, я понял, то мета должно решать проблемы рефлекшна, но не очень понял, что с рефлекшненом, что из этого будет стандартом, что будет стандартом мета. Я запутался в этих хитросплетениях. Что собрались стандартизировать, и что потом передумали стандартизировать?

Е. Ок, давай разложим по полочкам. Как я упоминал, осенью 11-года поступила идея сделать макросы в Скале, и мы сели все это делать. Что такое макросы? Макросы – это функции, вызываемые во время компиляции и получают программу, которая сейчас компилируется, вернее, часть этой программы, аргументы свои, и что-то делает над этими аргументами, после чего генерирует результирующий код. Пара примеров: макрос, который генерирует сериализаторы. На вход принимает тип, для которого нужно сериализатор сгененировать, а на выход возвращает анонимного наследника этого сериализатора. Класс, который расширяет, скажем, Serializer[T] и что-то делает, реализует метод serialize. Второй пример макроса – Spores. Моя коллега, Хезер Миллер, сейчас занимается несколькими проектами по улучшению распределенных вычислений в Скале, и один из них – в том, чтобы удостовериться, что те данные, которые мы сериализуем, не содержат никаких ссылок на то, что нельзя передать по сети. Например, маленькая структура данных, которую мы хотим передать по сети, содержит в себе ссылку на Википедию, которая у нас закэширована, и мы не хотим не допустить того, чтобы эта маленькая структура данных потянула за собой несколько терабайт данных. Итак, здесь нам поможет макрос. Если мы хотим передать какую-то функцию или замыкание по сети, мы её оборачиваем в макрос-spore, который смотрит свой аргумент и структуру лямда-выражения и убеждается в том, что он не ссылается на то, что не надо. В итоге макросы – функции, которые выполняются компиляции и могут делать со своими аргументами все, что угодно.

Итак, что нужно для того, чтобы макросы случились. Нужно представлять программу в виде структуры данных. Скажем, одно из самых типичных представлений – абстрактное синтаксическое дерево, именно его мы используем в наших макросах и скала-компиляторах. Для каждой языковой конструкции Скалы, будь то IF или вызов функции, определение класса нам нужна отдельная структура данный, кейс-класс, который будет моделировать эту языковую конструкцию, а также некоторая информация о типах, символах и т.д. В итоге получается куча вещей, которую нужно как-то упаковать и сделать доступной авторам макросов. Мы сделали это, добавив стандартную поставку языка библиотек scala-reflect. Соответственно, если кто-то хочет написать макрос, он пишет функцию, которая принимает специальный контекст, который объявлен в этой библиотеке, принимает ряд аргументов и параметров и что-то с ними делает. В таком виде макросы попали в состав Скалы 2.10 в качестве экспериментальной функционаьности. Есть у нас scala-reflect, такой пакет с неудивительным названием, и дальше нам нужно что-то делать. После релиза Скалы 2.10 макросы оказались очень полезны, и их стали использовать ведущие библиотеки из нашей экосистемы, и поэтому стал вопрос стандартизации, чтобы убрать статус экспериментальности, чтобы большие игроки могли использовать макросы без опасений насчет обратной совместимости.
Ок. Теперь можно понять, откуда появилась идея сделать какой-то стандарт, и мы стали заниматься этим направлением. Вскоре выяснилось, что существующий интерфейс скалы-рефлект, который является частью внутренностей компилятора, который мы отделили от него, не очень дружественен для пользователя.

В.: Секундочку. Но зачем пользователь это видит? Я так понимаю макрос — это просто метод, который просто берет и вместо определенной функции подставляет свое тело и обычно в таких манипуляциях с кодом используется квази-квотирование, или здесь что-то другое? Как раз через рефлекш дерево синтаксическое вытаскивается?

Е. Действительно, макрос – это просто функция, которая принимает на вход синтаксические деревья, и ими можно манипулировать при помощи квази-цитат. Как собирать деревья так и разбирать. Это новая функциональность появилась в Scala 2.11. Но неважно, главное, что она уже есть. При чем же здесь внутренности компилятора? Дело в том, что наши макросы отличаются от макросов в других языках тем, что у нас гораздо более широкий API доступен. Например, в Лиспе вообще нет типов, но в Скале они есть, и мы решили, что пользователям макросам неплохо бы иметь возможность узнать типы деревьев, которые приходят на вход, и их частей. Где это может быть полезно? Хоть в прошлом примере. Когда мы генерируем сериализатор для какого-то типа, берем тип, получаем информацию, например, список полей, и на её основе сгенерируем код, который нужен для сериализации. Полностью статически во время компиляции, не прибегая к рефлексии во время исполнения программы. Это все очень классно, люди используют такую возможность, интеграцию наших макросов с тайпчекером, но с другой стороны это увеличивает поверхность API. Нашему macro engine приходится работать гораздо больше, чем в если бы макросы ничего не знали про типы. Здесь получается, что пользователям макросов нужно иногда знать некоторые инварианты, которые тайпчекер ожидает от деревьев, которые ему скармливают, тонкости и отличия типизированных и нетипизированных деревьев. Мелочи накапливаются, порождая не лучший опыт для пользователя. Я ответил на вопрос?

В. В общем, да. Но ты ответил на вопрос, в чем отличие рефлекшна от мета.

Е. Да.

В. Вот, смотри. Очень многие спрашивают, когда наконец то, что обещано в мете, то, что… предполагалось макро-аннотации, для добивания класса какими-то другими методами, чтобы макросы были менее локальными… Я так понимаю квази-квоты уже в стандарте… Бандлы планировались… white/black-box разделение… Когда все это будет в стабильном релизе?

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

В. Там же еще были вещи, которые не относились к мете? То есть макро-аннотации, бандлы, разделение макросов на white/black-box.

Е. Все верно. Можно сказать, что развитие макросов можно наблюдать по двум осям. Одна – улучшение API, который видят пользователи. Раньше у нас деревья можно было создавать только вручную, с помощью вещи, которая известна, как reify, а сейчас у нас квази-цитаты, которые более гибки и более удобны для пользователя. Благодаря движению по этой оси макросы становится легче понимать, и все больше людей их может использовать. Другая ось – в наращивании функционала макросов. В самом начале у нас были только def-макросы, т.е. подобные обычным функциям, которые точно также вызываются, и которые раскрываются во время компиляции. У нас была куча интересных идей, например, макро-аннотации. Свешиваем аннотацию на какой-нибудь класс, метод, val, и происходит раскрытие макроса, который генерирует новый код. Развитие по этой оси – в добавлении все новых и новых фич, которые могут как-то взаимодействовать с макросами. Если рассматривать то, что ты сказал, это относится ко второй оси. И пока движение в этом направлении более-менее заморожено, потому что после обсуждения с TypeSafe мы решили, что в начале нам нужно сделать API очень хорошим и стабильным, а потом думать о наращивании функционала. Это касается макро-аннотации. Что касается white/black-box макросов, это была интересная инициатива в момент разработки скала 2.11, и она сейчас уже реализована, стало доступно разделение между этими макросами, но не буду вдаваться в детали.
Наконец, про макро-бандлы. Это была функциональность для улучшения удобства пользователя макросов, т.е. в нашей первой инкарнации макросы могли объявляться только в объектах, и они должны были транслироваться в статические методы, если рассматривать то, как все компилируется в JVM. Наши пользователи быстро поняли, что это часто порождает синтаксические неудобства. Не буду вдаваться в детали, это связано с path-dependent типами, но понадобился дополнительный более удобный способ записи макросов. В связи с этим появились макро-бандлы, которые были успешно реализованы и смерджены где-то в 2.11.05 или 06, сложно вспомнить. Резюмируя, скажу. Развитие макросов ведется в двух направлениях – в сторону упрощения API и в сторону добавления новых возможностей языка, которые могут взаимодействовать с макросами. Пока что мы сфокусированы на API, т.к. хотим стабилизировать основание, после чего будет наращивать функциональность. Мне кажется, это правильный шаг. Что касается макро-аннотация и других интересных экспериментальных фич, есть плагин к компилятору, macro-paradise, который позволит этим насладиться. Ценой того, что отполированность этой функциональности будет несколько меньше, чем в стандартной поставке Скала.

В.: А, например, type-провайдеры, насколько я понимаю, они раньше каким-то образом эмулировались через локальные макросы, потом была идея сделать функциональность, чтобы сделать их лучше. Как с этим сейчас?

Е. Ок. Type-провайдеры – интересный для нас сценарий использования, важный исторически, имеет большое сентиментальное значение имеет. Дело в том, что когда начинались макросы, начинался проект Slick, когда TypeSafe делал первые шаги, и Slick был первым совместным проектом EPFL и TypeSafe. Как ты знаешь, Slick – это библиотека для доступа к данным, которая сейчас вовсю разрабатывается TypeSafe. Изначальной идеей было сделать (как было записано в нашей заявке о гранте) что-то вроде LINQ, интерфейс, который позволяет писать обычный scala-код, представлять базу данный в виде коллекций и записей, и использовать стандартные функции высшего порядка вроде filter, map и т.д., чтобы потом гененировался SQL. Мы получаем совместимость со стандартным интерфейсом к базам данных и удобный интерфейс для пользователя. Это круто, и мы это реализовали с помощью макросов. Вторая часть — Type-провайдеры. Как мы видели из опыта коллег в C# и F#, полезно иметь функциональность, которая позволяет стучаться во внешние источники данных, узнавать их схемы, генерировать заглушки, структуры данных, которые будут повторять эти схемы. Так как мы планировали библиотеку доступа к данным следующего поколения, мы не обошли вниманием эту возможность. Но просто на def-макросах, то есть макросах, которые выглядят, как методы, это реализовать не получилось. Нужно было что-то побольше, что могло бы создавать новые классы, которые публично видны всей остальной программе. Эту функциональность мы окрестили type-провайдерами. В принципе type-провайдеры в F# это и делают. На текущий момент известно два способа эмуляции этого. Первый – использование def-макросов с небольшими ограничениями. Def-макросов и структурных типов для того, чтобы делать что-то подобное. Со структурными типами связано несколько проблем, но, в принципе, если есть охота пописать макросы, а потом еще макросы, которые исправляют эти проблемы, то можно добиться вполне работающего решения, которое вполне совместимо с функциональностью, которую будет доставлять Скала 2.11. Второй подход – использование макро-аннотаций, которые мы уже недавно упоминали. Идея в том, что мы пишем пустой object, навешиваем макро-аннотацию, а потом соответствующий макрос либо стучится в нашу базу данных, либо из ресурсов проекта читает схему базы данных и заполняет пустой object определениями, соответствующими таблицам и колонкам базы. Итак, у нас есть два способа реализации, эмуляции type-провайдеров. Первый работает прямо сейчас, но имеет ограничения. Второй требует макро-аннотации, которая доступна только с плагином компилятора, и вряд ли они будут доступны в ближайших версиях Скалы. В начале мы хотим сделать хороший API.

В. Спасибо. Я так понимаю, что все, что мы выше обсуждали, говорит о том, что делать макросы для Скалы – очень нетривиально и сильно отличается от многих других систем макросов. Можешь ли ты рассказать о других уникальных вызовах, которые перед тобой вставали, когда ты проектировал и реализовывал макросы для Скалы. Какие сейчас перед тобой стоят вызовы в мете?

Е. Это очень интересный вопрос. Можем говорить часами. Как ты сказал, сейчас все то, что макросы Скалы сочетают в себе уникальную комбинацию фич, являются экспериментальными, т.к. мы должны решить, как фичи будут взаимодействовать друг с другом, и пользователи могли их использовать не прострелив себе ног. Это в общем. В частности, к примеру, сегодня в нашей лаборатории мы давали презентацию для студентов на тему семестровых проектов, которые будут доступны со следующей весны. И одна из самых главных тем – как подружить макросы с различными тулами нашей эко-системы. Оказалось, что макросы – очень радикальная функциональность для языка, в котором раньше они не предполагались. Тут дело в том, что все тулы, которые уже собрались в нашей экосистеме например, инкрементальная комплиляция при помощи SBT, среды-разработки плагины для Eclipse и IntelliJ, какие-то средства для анализа кода тоже существуют. Была отличная презентация недавно на Pacific Northwest Scala. Все эти программные средства изначально ничего не знали про макросы, и тут они внезапно появились, то есть такие функции, которые могут генерировать код, которого раньше нигде не было и который не отображается ни в каких файлах, и этот код нужно как-то учитывать. Конкретный пример – SBT. Задача его в том, чтобы узнать, какие части кода или проекта зависят от каких других частей проекта, чтобы мы могли перекомпилировать зависимости при изменениях. Что делают макросы? Макросы с аргументами, поступающими на вход, могут делать все, что угодно. Например, извлечь интересные части и выкинуть остальное, сгенерировав новый код. В процессе мы теряем информацию о том, какие именно у макроса были с аргументами, и от чего макрос зависел. Раньше если вы используете что-то в аргументах макросов, и это меняется, то макросы не будут перераскрыты. Это была проблема, т.к. приходилось очищать sbt-проект и начинать заново все компилировать. Неудобно. Еще один пример – тоже с SBT. Как я говорил, макросы могут делать все, что угодно. Например, постучаться к какому-нибудь типу и спросить список его методов. SBT пока не способен запоминать такую информацию. Если в какой-то класс, в которой мы постучались, мы добавим новый метод, макрос об этом не узнает. Все это очень интересно и показывает, как глубоко должна быть продумана интеграция макросов в сопровождающие программы, в тулчейн соответствующей экосистемы. Это большое открытие для меня. И до сих пор все проблемы не решены. Однако в скала-мете у нас есть план, как победить все трудности. Так всегда: если есть новая система, она должна быть лучше старой.

В.: Понятно, спасибо. Среди трудностей, которые тебе приходилось перебороть, была такая бага – SI-6240, которая, как я понял, сильно задержала релиз Скала 2.11. Насколько я помню, бага связана с тем, что не было понятно, насколько хорошо рефлекшн работает с multi-threading. Все так?

Е. Отчасти, да. Действительно, был такой баг, один из немногих, которые я помню по номеру, до сих пор ужасает. История интересна, и сейчас все по порядку. Как я говорил выше, наш текущий API для мета-программирования, известный как скала-рефлект, появился на свет из-за того, что какую-то часть внутренней реализации компилятора отделили от основного компилятора и положили в отдельный jar. Все было круто, если бы компилятор изначально писался с расчетом на то, что какая-то из его частей станет видна широкой публике. Это было не так, и отделение породило ряд проблем. Одна из них – в том, что компилятор не предназначен для запуска в многопоточном режиме. Так случалось, что возможно это было сделано для улучшения производительности, возможно, по каким-то другим причинам, но в компиляторе есть ряд кэшей, которые работают исключительно в однопоточном режиме. В то время, как компилятор работает (нормальным образом), скажем, мы ему при помощи командной строки передаем список файлов, он их сначала парсит, потом тайпчекает, потом генерирует байт-код, это все нормально. Потому что совершенно непонятно, как сделать параллельный тайп-чекинг для языка вроде Скалы, где могут быть круговые зависимости, где все зависит от всего, и многопоточность никакой роли не играет. Однако когда однопоточная часть компилятора стала частью API, который может использоваться многоточной среде, встало много проблем. Как мы можем решить все проблемы? Самый простой способ – понавешать везде локов, вокруг кэшей. Примерно такой и получилась наша апроксимация, наш первый фикс этого бага. К сожалению, в результате некоторых недопониманий между тем, как нам лучше всего и надёжней починить этот баг, фикс не был включен в версию Скала 2.10.1, хотя в то время он у меня уже был готов. Так он просуществовал до поздней стадии релиза Скала 2.11. Все еще в качестве пул-реквества, все еще не был смержен, мы регулярно его дорабатывали, и так бы все и продолжалось. Возможно, так бы мы его в Scala 2.11 и не пофиксили, если бы не случилась удачная задержка релиза. Как ты верно упомянул, этот баг был починен очень поздно, но сам по себе не стал причиной задержки. Нам было полезно, что ребята из Scala-team в TypeSafe’е были заняты другими вещами, и пока они были заняты, мы смогли довести до ума наш пул-реквет. В итоге его смерджили и теперь мы считаем, что есть хорошая вероятность починки проблемы с многопоточностью в скала-рефлекте починены, но точный ответ даст только практика. Буквально недавно один из пользователей обнаружил, что определенные кэши имеют сильные ссылки на объекты типа java.lang.Thread, (в некоторых местах мы создали такие кэши для улучшения производительности), и это предотвращает сборку мусора соответствующих потоков, что приводит к переполнению памяти. Такие открытия лишний раз подтверждают, что многопоточное программрование — реально сложно. Лучше его избегать.

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

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

В. Спасибо. Еще пара вопросов от наших пользователей. Один из них: когда собираются внедрять лямбды с JDK в Скалу? (На самом деле предыдущий вопрос тоже был не мой.)

Е. Ок. Я полагаю, вопрос в том, когда лямда-выражения в Скале будут компилироваться точно так же, как они сейчас компилируются в Java 8?

В. Да.

Е. Ок. Обычно в делах бэкенда я обычно участия не принимаю, потому что моя работа над макросами в основном требовала знания фронтэнда, т.е. тэпчейкера, но это довольно важный вопрос, поэтому удалось услышать ответ на него и не раз. Насколько я понимаю, текущий план TypeSafe – это выпустить Скала 2.12 с полной поддержкой Java 8, и эта поддержка будет настолько полной, что версии Java ниже восьмой просто не будут поддерживаться. Единственный таргет байт-кода, который будет доступен это JDK 1.8.

В.: Радикальненько.

Е.: С другой стороны, как обещалось (хотя, возможно, все поменяется, и я не могу за TypeSafe официально говорить), Scala 2.12 будет по функциональности точно такой же, как Scala 2.11, кроме поддержки Java 8. Соответственно, если кто-то хочет использовать Java 1.6, то можно использовать Scala 2.11. Если кто-то хочет использовать 1.8, подойдет Скала 2.12. Других различий нет. В принципе, такое радикальное решение оправдано.

В. И последний вопрос. Каково твое личное мнение об Akka? Программировал ли ты на каком-нибудь Эрланге, например?

Е.: Это первый вопрос за сегодня, на который у меня нет ответа. С Erlang я знаком только понаслышке, вот недавно слышал доклад про него, удалось поприсутствовать. И Akka в жизни применить ни разу не удавалось. Хотя нет, вру. Помню, работал над Ensime. Это сервер, вернее, бэкграунд-процесс, который запускается на определенном SBT-проекте, и потом может предоставлять разную семантическую информацию по запросу. Таким текстовым IDE как Emacs или Sublime. В бэкграунде этот процесс запускает скаловский компилятор и общается с ним по какому-то интерфейсу для того, чтобы узнавать вещи, что обозначает идентификатор, откуда он был импортирован, есть ли какие-то ошибки в данном окне кода и т.д. Скаловский компилятор однопоточный, и с ним нужно работать аккуратно. Для этого в Ensime используются экторы, и это был единственный момент в моей жизни, когда я использовал экторы. Только я не скажу точно, были ли это akka-акторы, которые добавили в Скале 2.11, или это были старые scala-actors.

В.: А сейчас Ensime развивается?

Е.: Я все еще подписан на его mail-list, и хоть уже нет времени заниматься самостоятельно, и там довольно регулярно появляются сообщения и коммиты в репозитории.

В. Спасибо большое, с тобой было очень интересно пообщаться, ты очень здорово все рассказываешь. Я прям просветлился. Спасибо большое.

Е.: Спасибо большое.

В.: Пока. С вами был я, @sumerman, Женя @xeno_by. Лайкайте, пишите комментарии, подписывайтесь. Всем спасибо, всем до следующего выпуска.

Ах да, как вы, уважаемые слушатели, могли заметить, этот выпуск довольно существенно задержался. Это было связано с некоторыми техническими накладками. Мы многократно переносили запись. Я болел, не было времени свести. Тем не менее, он наконец состоялся. Это последний, десятый выпуск в этом сезоне. Сезон я хотел закончить еще в начале ноября и уйти на зимние каникулы, но только в декабре он выходит, и в этом году выпусков не будет больше. Я надеюсь, что подкаст вернется в третьем сезоне в начале весны. Всем пока, всем хороших каникул и счастливого Нового года.

Метки: .


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