Я постоянно замечаю, что «О, святые небеса, Scala — это сложно!». Знаете, возможно это правда. Давайте попробуем посмотреть на эту сложность и пройтись по основным отличиям Scala от других объектно-ориентированных языков (например, Java или C++).
Вывод типов
В большинстве случаев, компилятор способен вывести тип, что позволяет разработчикам писать меньше повторяющейся информации, которую они так привыкли писать в Java и C++.
К примеру, совершенно ясно, что «john» это строка, так что компилятор может вывести тип самостоятельно, и мне не придется указывать тип явно. Я могу просто написать:
val aGuy = "John"
Я встречал как одобрение так и критику, касающуюся вывода типов, так что даже и не знаю, что сказать. Лично мне нравится писать меньше и чувствовать себя чуть менее глупым всякий раз, когда я объявляю значение или переменную.
Упрощенное определение классов
Так как OOP строится на основе определения классов, то в Scala это реализовано максимально просто. Все мои доменные модели получаются предельно простыми.
class Person (val firstName:String, val lastName:String)
Надо сказать, что тоже самое в Java или C++ заняло бы почти страницу кода со всякими там конструкторами, геттерами, сеттерами и т.д. Разработчики Scala поняли, что люди не должны писать целую страницу кода лишь ради того, чтобы сообщить глупому компилятору, что им нужен класс Person, у которого есть два поля: имя и фамилия. Так что, в Scala, определение такого класса занимает всего лишь одну строчку, так же как выглядела бы таблица в SQL.
Добавляет ли это сложности?
Ну… Мне нужно обеспокоится разве что в тех случаях, когда мне нужно переопределить сгенерированные геттеры и сеттеры. Так что я даже и не знаю, действительно ли это усложняет жизнь…
Я?! Лично я настолько в восторге от этой возможности, что, честно говоря, мне абсолютно всё равно, что вы там себе думаете.
Объединение методов и операторов
Все языки программирования, которые я знаю, проводят жесткую грань между методами с именем, типа «append», и операциями, типа "+=". Некоторые языки, запрещают переопределение существующих операторов (Java). Другия языки разрешают только инфиксную нотацию для операторов (C++). В Scala нет всех этих ограничений. Вы можете использовать любые имена для названия методов, а инфиксная нотация разрешена для любых вызовов:
ListBuffer(1,2,3) append 4
// И так тоже можно
ListBuffer(1,2,3) += 4
Единственное отличие будет состоять в правилах приоритетности. Конечно, кто-то скажет, что это «более сложно», чем в Java или C++, потому что это «развязывает руки» и позволяет делать всё что хочешь. Однако, я считаю, что это более просто, чем в упомянутых языках. В конце концов, это делает Scala совершенным фреймворком для создания естественного DSL:
"Mary" with "a purse" and "red shoes" should look "nice"
Типы
И в Java и в C++ шаблоны имеют определенное зашитое ограниченное поведение (т.е. ковариантность) и позволяет только несколько конструкций (наподобие List<T extends Person>).
В Scala есть дефолтное поведение, при котором List[Person] невариантен, но всё можно настроить. Если вам нужна ковариантность, просто скажите компилятору List[+Person] или List[-Person] для контрвариации. На подобии Java, есть конструкция List[T <: Person] и List[T >: Person]. Так как в Scala есть механизм неявных преобразований (implicits), то также доступна еще одна конструкция, как раз для таких случаев: List[T <% Person].
Сложнее ли это?
Так же как и с операторами, этот механизм порождает некоторые ограничения, однако, с другой стороны предоставляет новые возможности. Лично мне нравится этот контроль типов. Пользовался ли я этим? Не в каждодневной разработке, чаще всего хватает обычного дефолтного поведения контроля типов.
Только конструкторы?
Большинство языков позволяет только конструировать объекты. Scala позволяет также деконструировать объекты, при этом я не говорю об освобождении памяти, занимаемой объектом. Рассмотрим:
someone match {
case Person(first,last) => println (s" name is $first $last")
}
Здесь вы можете увидеть, что я подразумеваю под «деконструкцией» объекта. Возьмем уже созданный объект и деконструируем его на компоненты first и last. Я знаю, это выглядит чуждно и непривычно для большинства сторонников OO, но поверьте мне, это безумно круто и удобно! Представьте, что вам пришлось бы сделать, для того чтобы реализовать подобное в Java или C++… Проверить на тип (instanceof), присвоить две переменные на эти значения и еще много чего…
Вспомните про интерполяцию строк в Scala:
"$first $last"
Это более сложно? Ну… Это совершенно новый функционал. Мне нравится! Поверьте, вам тоже это понравится!
Вот, кстати, match/case предоставляет более широкий спектр возможностей, нежели обычный switch/case, который умеет работать только с константами. С помощью match/case можно деконструировать объекты, матчить константы, матчить типы, и многое, многое другое! Да посмотрите сами:
someone match {
case Person(first,last) => println (" name is " + first + " " + last)
case "John" | "Mary" => println ("hello, " + someone.toString)
case s:String => println ("name is " + s)
case _ => println ("don’t know what this is…")
}
Это сложно? Я не знаю… в Java или C++ это заняло бы от одной до двух страниц кода. Лично для меня, в Scala это выглядит просто и интутивно понятно.
Заключение
Конечно, это лишь некоторые аспекты языка. Если вам есть что добавить, пишите пост, буду рад.
Я не касался функционального программирования на Scala, для этого нужно сравнивать Scala с другими функциональными языками, он я не FP'шник (FP guy). С++ сейчас позволяет передавать указатели на функции в другие функции, а в Java 8 введены lambda конструкции.
Сложно ли всё это? Ну… Можно посмотреть с двух сторон на всё это:
Да,
- Больше символов и возможностей, которые можно использовать
- Нужно учить некоторые дополнительные термины и понятия из области компьютерных наук (контравариация, паттерн матчинг, лямбды, замыкания и т.д.)
Нет,
- Реальные проблемы решить можно
- Такие же конструкции на Java или C++ сделать впринципе невозможно или это займет гораздо больше кода… и… код будет выглядеть ужасно.
Что я думаю? Мне реально всё равно. Лично я очень рад, что познакомился с этими возможностями. Это позволяет мне решать проблемы всего лишь в несколько строчек кода, в то время как мои друзья всё еще пишут эти геттеры и сеттеры…
Негатив
Есть всё же несколько вещей, которые мне не понравились, за чуть более чем шести летний опыт работы со Scala. Вот некоторые из них:
- Медленная компиляция. Не то чтобы раздражающе медленная, но всё же гораздо медленнее, чем у остальных.
- Неявные преобразования. Мне не нравится как они реализовали этот механизм. Можно случайно заимпортировать преобразование из Any в MyCoolThing, котороый был определен в каком-нибудь импортируемом блоке.
- Библиотека коллекций. Для большинства людей это не так важно, но… Всё это выглядит гораздо сложнее, чем мне хотелось бы.
- SBT. Это всегда сложно. Серьезно, если вам нужно сделать что-то выходящее за рамки типичной работы с SBT, то вы попали. Существует столько разных не очевидных DSL, что Google будет сходить с ума пытаясь найти для вас как нужно сделать то, что вам нужно. Им нужно было сделать либо какой-то отдельный синтаксис, наподобие JSON или какой-то свой внутренний DSL, который компилятор / IDE мог бы понимать и помогать при работе с SBT.
P.S. Более детальная дискуссия насчет сложности: Simple or Complicated?
Автор: jxcoder