orhideous' blog

consciousness and code

Scala: впечатления и CanBuildFrom[Map[Велосипеды, Грабли]…

Раньше со Scala я сталкивался изредка, присматривался и только думал писать на этом языке что-то для себя, полный стереотипов «оооо, как всё тут сложно».

Однако новые вызовы и потребности решать рабочие задачи на чем-то более продвинутом привели меня к изучению этого языка посерьёзней, чем простой «Hello world».

Поскольку я несколько лет плотно работаю с созданием/использованием Python'овского кода, то в записи будут мелькать сравнения с Python'ом; как по мне, так проще провести понятные мне аналогии. Опыт работы с новым языком только-только формируется, и это наполовину вынужденное знакомство со Scala оставило ряд противоречивых впечатлений.

Да, Scala сложна!

Первое впечатление обманчиво. Туториалы не предвещают никакой сложности, но тривиальные задачи вида «стянуть по сети архив, распаковать в память, распарсить содержимое и построить с него структуру данных» подобно ведру холодной воды на голову резко отрезвляют.

«С чего начать?…»

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

Бесполезный StackOverflow

SO — не тот ресурс, на котором стоит искать ответы на вопросы вида «как мне?…». В случае Scala он бесполезен, потому что язык подразумевает понимание задаваемого вопроса. На SO в лучшем случае есть только копипаст без объяснения, КАК оно работает.

Иначе же, нагуглить ответ…

как мне свернуть List в HashMap?

…можно быстро, потом вставить этот таинственный кусок символов, и полчаса разбираться, почему он не работает. А можно за то же время прочесть документацию и самому всё понять.

Почти бесполезный REPL

Язык компилируемый, и для того же «Hello World» недостаточно просто написать несколько строк и как-то запустить — нужно скомпилировать, и не просто со scalac — надо настраивать gradle. Хотя, может, для однострочника хватит и scalac.

Это сразу разрывает привычный многим разработчикам-новичкам цикл REPL, используемый не как рабочий блокнот, а как песочница.

  1. что-то написал
  2. как-то запустил
  3. получил какую-то ошибку
  4. погуглил
  5. исправил
  6. GOTO 1

В Scala это не работает, и для эффективной работы приходится менять привычки с учетом того, что запуск дорогой по времени, и код писать надо сразу рабочий (по возможности).
Да, с ростом навыков REPL используется по прямому назначению — как место для идей и заметок, но как средство изучения языка он неудобен.

f(t: Type[_])(implicit all: (the, things)) = ???

Типы. Они везде. Система типов мощная, стройная, и местами сигнатуры методов очень кучерявые. К счастью, адских порождений Некрономикона не так уж и много, хотя бывает и такое…

def unlift[A](implicit FG: Arr[F, G] <~< (F ~> G), GF: Arr[G, F] <~< (G ~> F)): F[A] <=> G[A] =
    new (F[A] <=> G[A]){
        def from = GF(self.from).apply
        def to   = FG(self.to).apply
    }

…что можно вызвать дух самого Хаскелла Брукса Карри, если прочитать это в пентаграмме.

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

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

Изменяемое состояние? return? NO WAY!

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

«Ключевое слово» return тоже есть, но его использование считается очень грязным хаком, и не зря.

Так и приходится писать — без переменных, без return. После языков, где не возбраняется переопределять что угодно и досрочно выйти откуда угодно такие ограничения ошеломляют, но в Scala есть ФП!

Функциональщина

Тут она везде. Изменение какого-то внутреннего состояния в Scala можно сделать гораздо изящнее, элегантнее, проще — не в пример циклам или императивному подходу.

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

Но не всё так радужно: вся эта магия становится интуитивно простой и понятной только после определённого упорства и читания исходников, примеров; без определённого стартового набора знаний о ФП все эти свертки, сопоставления с образцом и монады выглядят со стороны какой-то тёмной магией, к сожалению.

Коллекции

Есть какие угодно, на всякий вкус. Но реализация под капотом чересчур усложнена. Даже разработчикам языка это не нравится; к их чести, в 2.13 ситуация изменится к лучшему.

Но для новичка всё же стоит потратить несколько вечеров и разобраться, что там есть, кроме Array, Vector, Stack и List, дабы не умножать свои велосипеды.

Отдельно отмечу синтаксис для работы с коллекциями; если к seq1 ++ seq2 или там 1 :: 2 :: 3 :: Nil со временем привыкаешь, то на (lst /: Map.empty[Int, Int]) { (a, b) => … } вместо нормального foldLeft без грусти не посмотреть.

it should be "free DSL" in { scala }

Выразительные возможности языка даже чересчур большие, чем я от него ждал. Любую (sic!) задачу можно решить тремя разными простыми способами, двумя неочевидными, дюжиной сложных и одним таким, что энтерпрайзный FizzBuzz в сравнении с ним прост и понятен.

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

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

Экосистема

Количество готовых инструментов для вообще всего огромное. Мне, как новичку, было очень трудно после уютного и привычного PyPI разобраться не то, что в подходящих — даже в популярных библиотеках.

Simplifying Sparky Stuff

Отдельные вещи в Apache Spark удобнее писать на Scala, да и Python'овские API до сих пор поддерживается не столь полноценно — правда, ситуация начала меняться в гораздо лучшую сторону.

Так стоит ли связываться?..

Точного ответа на вопрос у меня пока что нет.
Какие-то одноразовые прототипы или непритязательные по сложности сервисы привычней и легче писать на интерпретируемых языках, время до выкатки на продакшен гораздо меньше.
Но при выборе инструмента для разработки чего-то посложнее микросервиса либо с серьёзными требованиями по надёжности/производительности я теперь буду рассматривать всерьёз и Scala.

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

Очень сложно.

Author image
Об авторе Andriy Kushnir
Ukraine, Kyiv Сайт