Здравствуйте, коллеги.
Не так давно мы допечатали книгу Одерски, Спуна и Веннерса о Scala 2.12. Ведь до Scala 3 еще далеко.
Автор сегодняшней статьи — Адам Уорски, сооснователь компании «SoftwareMill» и опытный Scala-разработчик. У него получилось интересное резюме сильных сторон современного языка Scala, которое мы и предлагаем вашему вниманию.
После пленарной лекции Мартина Одерски на ScalaDays, где он изложил планы на Scala 3 и пленарной лекции Джона де Гоуса на конференции Scalapeño о будущем Scala в сообществе Scala продолжаются оживленные дискуссии. Не считая обсуждения этих пленарных выступлений, многочисленные дискуссии идут в twitter, в сообществах Slack, а также на reddit (см. напр. 1, 2).
Вот в чем вопрос!
Все это очень интересно и познавательно для специалистов, уже работающих в экосистеме Scala. Однако, значительная часть споров связана с различными интуитивно ощутимыми или фактическими недостатками Scala как языка. Эти моменты могут отпугивать людей «со стороны», вызывая у них вопросы: «А зачем мне вообще пользоваться Scala?», «А вдруг это тупиковый путь?», «Повторит ли Scala 3 успех Python 3?» и т.д. Как и в большинстве дискуссий, внимание приковывают самые слабые и эмоциональные тезисы, и эти дебаты — не исключение.
Поэтому сделаем шаг назад: зачем вам вообще мог бы потребоваться язык Scala? Каковы технические и деловые доводы в пользу работы с эти языком?
Предметная область проекта
Начнем с того, что язык Scala особенно хорош для определенных предметных областей (но не для всех!). Важнейшее достоинство Scala – это гибкость при определении абстракций. В вашем распоряжении – ряд простейших «кирпичиков» для этого; иногда определить абстракцию не сложнее, чем использовать класс, методы и лямбда-выражения. Иногда приходится использовать неявный параметр или метод расширения; в редких случаях остается только прибегать к макрокомандам. Однако, есть варианты.
Таким образом, Scala отлично вам подойдет, если требуется сориентироваться в сложной предметной области. Например, речь может идти о распределенном или конкурентном программировании. Наладить параллелизм очень сложно, а в Scala есть ряд библиотек, упрощающих эту задачу путем построения абстракций. Для этого существует два основных подхода: акторный, реализуемый при помощи Akka, и в духе FP, представленный Monix/cats-effect и Scalaz/ZIO (если бы вы хотели подробнее почитать о сравнении этих инструментов – я написал серию статей как раз на эту тему).
Однако, разумеется, речь может идти и о других предметных областях. Возможности Scala также позволяют вывести на новый уровень моделирование типичных бизнес-приложений. Но в данном случае речь уже о сложности другого порядка. В случае с распределенными системами мы сталкиваемся с технической сложностью. При программировании бизнес-логики в приложениях речь идет уже о сложности предметной области как таковой. Например, в книге Дебасиша Гхоша (Debasish Ghosh) “Functional and reactive domain modeling” объясняется, как совместить DDD с функциональным и реактивным программированием.
Сложность языка
Отметим: рассказывая о Scala, все любят подчеркивать широту возможностей этого языка, подчеркивая, что именно по причине такой разносторонности этот язык столь сложен. Это не совсем верно.
Как упоминалось выше, в Scala есть ряд базовых конструкций, из которых можно собирать абстракции. Однако, все их можно комбинировать друг с другом, благодаря чему язык и приобретает такую гибкость. Большинство новаторских проектов, которые могут вам попадаться, сводятся к тем или иным сочетаниям типов высшего порядка, неявных параметров и выделения подтипов.
Таким образом, при относительно небольшом количестве базовых возможностей (по размеру грамматики Scala проще Kotlin, Java или Swift!) — что, в свою очередь, облегчает обучение – варианты комбинаций гораздо многочисленнее.
Не слишком ли широкий выбор? Не думаю. Программист, как компетентный и ответственный профессионал, более чем в состоянии выбрать наилучший вариант, чтобы решить конкретную задачу. Подробнее об этом см. в статье “Простой стек Scala”.
Java, только лучше
Сейчас много говорят, что Kotlin вытеснил Scala в качестве “как Java, только лучше”. Но я считаю, что именно Scala, а не Kotlin, по-прежнему заслуживает такого звания. На то есть две основные причины:
Во-первых, неизменяемость в языке Scala первостепенна. Это связано с природой Scala как таковой: писать на нем код удобно прежде всего при помощи неизменяемых данных. К таким данным относятся, в частности: val
(как единицы первого класса), case
-классы, функции высшего порядка, т.д. Но дело и в том, как спроектирована и написана стандартная библиотека языка Scala; неизменяемы все структуры данных, используемые «по умолчанию». Благодаря неизменяемости упрощается целый ряд вещей, особенно в нашем мире высокой конкуренции, где выигрывают именно те языки, где предпочитается неизменяемость.
Во-вторых, в Scala поддерживаются конструкторы типов, типы высшего порядка и типы классов (объявляемые неявно), что серьезно упрощает работу с оборачивающими/контейнерными типами, например, с Promise
, Future
или Task
. Такие обертки преобладают при программировании в асинхронном или реактивном стиле, и наличие языковых конструкций, упрощающих работу, скажем, с такой базой кода, где активно используются Future
– еще один аргумент в пользу Scala.
Что такое типы классов? Роль их примерно такова, как у паттернов проектирования в Java, однако, они более гибкие и простые в использовании, как только вы крепко усвоите их смысл. По этому поводу есть несколько отличных руководств, например, 1, 2.
Что насчет конструкторов типов? Это такие типы как Future
или Option
, служащие “контейнерами” или “обертками” для других типов. Так, у вас может быть Option[String]
или Future[Int]
. Типы высшего порядка позволяют писать код, обеспечивающий абстрагирование любой обертки. См. например. 1, 2.
Вдруг вам интересно, зачем вообще прибегать к Future
/Task
? Дело не только в том, что большинство высокопроизводительных «реактивных» вычислений с минимальными задержками выполняются с применением асинхронного ввода/вывода, для которого просто просятся именно такие конструкции. Другие причины приводятся в этой дискуссии на reddit и в моей статье “Зачем возиться с обертками?”.
Работа в преимущественно неизменяемой среде, а также возможность с удобством обращаться с такими конструкциями как Future
, Promise
или Task
(и абстрагировать их!) радикально преображает ваш код. На первый взгляд это может быть неочевидно: если у вас за плечами опыт Java или Ruby, то осознать эти аспекты Scala вы сможете не сразу. Но, даже с образовательной точки зрения полезно разобраться, как устроен «функциональный» подход и, что гораздо важнее, почему он может оказаться весьма достойной альтернативой.
Естественно, и у Scala, и у Kotlin есть ряд общих преимуществ по сравнению с Java, например:
- Более компактный синтаксис
- Меньше стереотипного кода
- Более насыщенная система типов
- Отсутствие языкового балласта
В то же время, оба языка открывают доступ к экосистеме библиотек и фреймворков JVM.
Чем насыщеннее система типов (в Scala она богаче, чем в Kotlin, а в Kotlin – богаче чем в Java), тем больше работы по верификации выполняется компилятором, а не человеком. Именно для этого и созданы компьютеры: выполнять скучные, рутинные повторяющиеся задачи. Проверка применимости типов – определенно, как раз одна из таких задач.
При этом насыщенная система типов полезна не только при написании кода, но и, в гораздо большей степени при чтении кода. Когда тебе легко сориентироваться в базе кода, понять, что он делает, а, к тому же, не страшно (или не так страшно) браться за его рефакторинг, это очень положительно характеризует язык как с технической, так и с прикладной точки зрения.
ФП vs OOП
Еще один вопрос, часто поднимаемый в таких дискуссиях – должен ли язык Scala и далее придерживаться синтеза ООП и ФП, либо развиваться по чисто функциональному пути. Я – сторонник синтеза, особенно потому, что ФП и ООП на мой взгляд – не альтернативные подходы, а дополняющие друг друга.
ФП – это программирование при помощи функций, правда, не любых, а референциально прозрачных (см. этот ответ на reddit в более раннем треде, где превосходно объясняется референциальная прозрачность). В ООП коммуникация с объектами организована при помощи “сообщений” или, в нашей терминологии, вызовов виртуальных методов. Нет ни одной причины, по которой два этих подхода не могли бы сосуществовать, и Scala это уже продемонстрировал!
В твите о достоинствах Scala Джон де Гоус упоминает некоторые черты ООП, полезные и при чисто функциональном подходе, в частности, модули первого класса (получаемые путем композиции объектов), точечный синтаксис (вызов методов в объектах) и классы/экземпляры типов как конструкции первого класса. Все это – элементы успешной комбинации двух парадигм. Может быть, еще какие-то не за горами?
Проект «синтеза» еще не закончен, здесь определенно остается поле для обсуждения. Один из незавершенных аспектов – это предлагаемый синтаксис методов расширения, который должен прийти на смену гораздо более путаным неявным преобразованиям. Другой – оптимизация синтаксиса классов типов; предложение по этому поводу, высказанное некоторое время назад, явно сырое и не охватывает некоторые из наиболее распространенных случаев использования монад в Scala. Некоторые предложения лучше, другие требуют доработки, но хорошо, что они поступают, и о них идут дискуссии; благодаря ним язык живет и, в конечном счете, в нем закрепляется наилучшее решение.
Scala 3
Scala 3 выйдет через два года. В нем появится ряд интересных возможностей, в частности, непрозрачные типы, параметры типажей, новый подход, связанный с метапрограммированием, типы объединения и & пересечения, неявные типы функций – вот всего несколько примеров. Конечно, здесь возникает (обоснованная) обеспокоенность по поводу миграции.
Уже известно, что будет представлен специальный инструмент для автоматической миграции кода со Scala 2 на Scala 3 при помощи scalafix. Поскольку Scala – статически типизированный язык, такая задача решаема в больших масштабах и гораздо проще, чем, например, на Python. Но, разумеется, никакой магии тут нет: даже если этот инструмент на 99% обеспечит правильное покрытие кода, все равно останется 1% наиболее проблемных случаев. Поскольку на магию рассчитывать не приходится, миграцию этих фрагментов нужно будет выполнить вручную.
Таковы издержки работы с активно развивающимся языком: вы получаете новейшие возможности, но некоторые из них на поверку оказываются не столь хороши и нуждаются в доработке. Даже с учетом этого, предлагаемые изменения не являются революционными. Язык Scala 3 очень похож на Scala 2, никаких серьезных изменений парадигмы не предвидится.
Обнадеживает тот факт, что команда Scala всерьез подходит к миграции. Тогда как ранее миграция между старшими версиями Scala (напр. с 2.8 на 2.9) была довольно обременительна, последние миграции прошли гораздо лучше. В них участвуют три основных стороны: EPFL, ScalaCenter и Lightbend все они (зачастую вместе) работают на облегчение миграции. Например, существует двоично-совместимый инструмент MIMA, а также в сообществе постоянно создаются многочисленные новые библиотеки, призванные обеспечить удобную работу с новыми версиями Scala.
Наконец, пока не законченный инструмент TASTY (который, соответственно, пока нельзя оценить) должен обеспечить использование двоичных файлов из Scala 2 в Scala 3.
Итак, зачем же использовать Scala?
Итак, каковы же причины остановиться на Scala, если рассматривать этот язык с точки зрения бизнеса? Все вышеперечисленные технические преимущества напрямую полезны и для бизнеса. Когда у вас есть язык, на котором можно писать сложный код с меньшим количеством багов – то вам обеспечены и минимальные задержки сроков, и довольные пользователи. Если вы станете писать на Scala мощные, практически не пробуксовывающие конкурентные приложения, для которых в Scala предусмотрены специальные инструментарии, то это обернется серьезной выгодой для вашей компании.
Кроме того, не забываем и о Spark, ведущей платформе для распределенного анализа данных. Scala используется не только для реализации Spark как такового, но и для определения самих вычислений. Вот вам еще одна data science-ориентированная абстракция, скрывающая сложную вычислительную модель.
Резюме
Разумеется, в Scala есть и проблемы, но где их нет? Повод для оптимизма заключается в том, что множество людей, использующих Scala ежедневно, активно работают над улучшением инструментария, библиотек и самого языка. Могу предположить, что они продолжают работать со Scala именно потому, что, при всех имеющихся недоработках, ни один язык так хорошо не подходит для их предметной области.
Scala позволяет вам развивать свой стиль программирования, каким бы языком вы раньше ни занимались: Java, Ruby или только пробуете себя в программировании. Не существует однозначно выигрышного подхода к Scala; можно придерживаться или более императивного подхода Akka, или более функционального подхода Cats и Scalaz.
Вот где можно усмотреть проблему: в сообществе Scala есть фракции, придерживающиеся “реактивного”, “синтетического” OOП/ФП подхода и чистого ФП. Однако, на самом деле в этом заключается огромное преимущество. Благодаря такому разнообразию ведутся дискуссии, высказывается множество различных мнений с разных точек зрения. На этом, в свою очередь, отлично можно выучиться нестандартному решению задач и обогатить собственный инструментарий.
Какой бы путь в Scala вы ни выбрали, вас поддержит внушительное сообщество коллег, занимающихся разработкой библиотек и фреймворков; эти люди всегда готовы помочь и обсудить возникающие идеи.
.
Автор: ph_piter