Использование Scala, и Scala 3 в частности, дает много преимуществ. Трудно перечислить их все, но “топ десять” может выглядеть так:
- Scala сочетает в себе функциональное программирование (ФП) и объектно-ориентированное программирование (ООП)
- Scala статически типизирован, но часто ощущается как язык с динамической типизацией
- Синтаксис Scala лаконичен, но все же удобочитаем; его часто называют выразительным
- Implicits в Scala 2 были определяющей функцией, а в Scala 3 они были улучшены и упрощены
- Scala легко интегрируется с Java, поэтому вы можете создавать проекты со смешанным кодом Scala и Java, а код Scala легко использует тысячи существующих библиотек Java
- Scala можно использовать на сервере, а также в браузере со Scala.js
- Стандартная библиотека Scala содержит десятки готовых функциональных методов, позволяющих сэкономить ваше время и значительно сократить потребность в написании пользовательских циклов
for
и алгоритмов - “Best practices”, встроенные в Scala, поддерживают неизменность, анонимные функции, функции высшего порядка, сопоставление с образцом, классы, которые не могут быть расширены по умолчанию, и многое другое
- Экосистема Scala предлагает самые современные ФП библиотеки в мире
- Сильная система типов
1) Слияние ФП/ООП
Больше, чем любой другой язык, Scala поддерживает слияние парадигм ФП и ООП. Как заявил Мартин Одерски, сущность Scala — это слияние функционального и объектно-ориентированного программирования в типизированной среде, где:
- Функции для логики
- Объекты для модульности
Возможно, одними из лучших примеров модульности являются классы стандартной библиотеки.
Например, List
определяется как класс—технически это абстрактный класс—и новый экземпляр создается следующим образом:
val x = List(1, 2, 3)
Однако то, что кажется программисту простым List
, на самом деле построено из комбинации нескольких специализированных типов,
включая трейты с именами Iterable
, Seq
и LinearSeq
.
Эти типы также состоят из других небольших модульных единиц кода.
В дополнение к построению типа наподобие List
из серии модульных трейтов,
List
API также состоит из десятков других методов, многие из которых являются функциями высшего порядка:
val xs = List(1, 2, 3, 4, 5)
xs.map(_ + 1) // List(2, 3, 4, 5, 6)
xs.filter(_ < 3) // List(1, 2)
xs.find(_ > 3) // Some(4)
xs.takeWhile(_ < 3) // List(1, 2)
В этих примерах значения в списке не могут быть изменены.
Класс List
неизменяем, поэтому все эти методы возвращают новые значения, как показано в каждом комментарии.
2) Ощущение динамики
Вывод типов (type inference) в Scala часто заставляет язык чувствовать себя динамически типизированным, даже если он статически типизирован. Это верно для объявления переменной:
val a = 1
val b = "Hello, world"
val c = List(1,2,3,4,5)
val stuff = ("fish", 42, 1_234.5)
Это также верно при передаче анонимных функций функциям высшего порядка:
list.filter(_ < 4)
list.map(_ * 2)
list.filter(_ < 4)
.map(_ * 2)
и при определении методов:
def add(a: Int, b: Int) = a + b
Это как никогда верно для Scala 3, например, при использовании типов объединения:
// параметр типа объединения
def help(id: Username | Password) =
val user = id match
case Username(name) => lookupName(name)
case Password(hash) => lookupPassword(hash)
// дальнейший код ...
// значение типа объединения
val b: Password | Username = if (true) name else password
3) Лаконичный синтаксис
Scala — это неформальный, “краткий, но все же читабельный“ язык. Например, объявление переменной лаконично:
val a = 1
val b = "Hello, world"
val c = List(1,2,3)
Создание типов, таких как трейты, классы и перечисления, является кратким:
trait Tail:
def wagTail(): Unit
def stopTail(): Unit
enum Topping:
case Cheese, Pepperoni, Sausage, Mushrooms, Onions
class Dog extends Animal, Tail, Legs, RubberyNose
case class Person(
firstName: String,
lastName: String,
age: Int
)
Функции высшего порядка кратки:
list.filter(_ < 4)
list.map(_ * 2)
Все эти и многие другие выражения кратки и при этом очень удобочитаемы: то, что мы называем выразительным (expressive).
4) Implicits, упрощение
Implicits в Scala 2 были главной отличительной особенностью дизайна. Они представляли собой фундаментальный способ абстрагирования от контекста с единой парадигмой, обслуживающей множество вариантов использования, среди которых:
- Реализация типовых классов
- Установление контекста
- Внедрение зависимости
- Выражение возможностей
С тех пор другие языки внедрили аналогичные концепции, все из которых являются вариантами основной идеи вывода терминов: при заданном типе компилятор синтезирует “канонический” термин этого типа.
Хотя implicits были определяющей функцией в Scala 2, их дизайн был значительно улучшен в Scala 3:
- Есть единственный способ определить значения “given”
- Есть единственный способ ввести неявные параметры и аргументы
- Есть отдельный способ импорта givens, который не позволяет им потеряться в море обычного импорта
- Существует единственный способ определить неявное преобразование, которое четко обозначено как таковое и не требует специального синтаксиса
К преимуществам этих изменений относятся:
- Новый дизайн позволяет избежать взаимодействия функциональностей и делает язык более согласованным
- Это делает implicits более простыми для изучения и более сложными для злоупотребления
- Это значительно улучшает ясность 95% программ Scala, использующих implicits
- У него есть потенциал, чтобы сделать вывод терминов принципиальным способом, который также доступен и удобен
Эти возможности подробно расписаны в соответствующих разделах, таких как введение в контекстную абстракцию, а также раздел о given
и предложениях using
для получения более подробной информации.
5) Полная интеграция с Java
Взаимодействие между Scala и Java не вызывает затруднений во многих ситуациях. Например:
- Вы можете использовать все тысячи библиотек Java, доступных в ваших проектах Scala
- Scala
String
— это, по сути, JavaString
с дополнительными возможностями - Scala легко использует классы даты/времени из Java пакета java.time._
Вы также можете использовать классы коллекций Java в Scala, а для придания им большей функциональности Scala включает методы, позволяющие преобразовывать их в коллекции Scala.
Несмотря на то, что почти каждое взаимодействие является бесшовным, в главе “Взаимодействие с Java” показано, как лучше использовать некоторые функции вместе, в том числе как использовать:
- Коллекции Java в Scala
- Java
Optional
в Scala - Интерфейсы Java в Scala
- Коллекции Scala в Java
- Scala
Option
в Java - Scala traits в Java
- Методы Scala, вызывающие исключения в Java коде
- Scala varargs параметры в Java
Подробнее об этих функциях см. в этой главе.
6) Клиент & сервер
Scala можно использовать на стороне сервера с потрясающими фреймворками:
- Play Framework позволяет создавать масштабируемые серверные приложения и микросервисы
- Akka Actors позволяет использовать модель акторов для значительного упрощения распределенных и параллельных программных приложений
Scala также можно использовать в браузере с проектом Scala.js, который является безопасной заменой JavaScript. В экосистеме Scala.js есть десятки библиотек, позволяющих использовать React, Angular, jQuery и многие другие библиотеки JavaScript и Scala в браузере.
В дополнение к этим инструментам проект Scala Native “представляет собой оптимизирующий опережающий компилятор и облегченную управляемую среду выполнения, разработанную специально для Scala”. Он позволяет создавать бинарные исполняемые приложения в “системном” стиле с помощью простого кода Scala, а также позволяет использовать низкоуровневые примитивы.
7) Стандартные библиотечные методы
Вам довольно редко понадобится писать пользовательский цикл for
,
потому что десятки готовых функциональных методов в стандартной библиотеке Scala сэкономят ваше время
и помогут сделать код более согласованным в разных приложениях.
В следующих примерах показаны некоторые из встроенных методов коллекций, а также многие другие.
Хотя все они используют класс List
, одни и те же методы работают с другими классами коллекций,
такими как Seq
, Vector
, LazyList
, Set
, Map
, Array
и ArrayBuffer
.
Вот некоторые примеры:
List.range(1, 3) // List(1, 2)
List.range(start = 1, end = 6, step = 2) // List(1, 3, 5)
List.fill(3)("foo") // List(foo, foo, foo)
List.tabulate(3)(n => n * n) // List(0, 1, 4)
List.tabulate(4)(n => n * n) // List(0, 1, 4, 9)
val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10)
a.distinct // List(10, 20, 30, 40)
a.drop(2) // List(30, 40, 10)
a.dropRight(2) // List(10, 20, 30)
a.dropWhile(_ < 25) // List(30, 40, 10)
a.filter(_ < 25) // List(10, 20, 10)
a.filter(_ > 100) // List()
a.find(_ > 20) // Some(30)
a.head // 10
a.headOption // Some(10)
a.init // List(10, 20, 30, 40)
a.intersect(List(19,20,21)) // List(20)
a.last // 10
a.lastOption // Some(10)
a.map(_ * 2) // List(20, 40, 60, 80, 20)
a.slice(2, 4) // List(30, 40)
a.tail // List(20, 30, 40, 10)
a.take(3) // List(10, 20, 30)
a.takeRight(2) // List(40, 10)
a.takeWhile(_ < 30) // List(10, 20)
a.filter(_ < 30).map(_ * 10) // List(100, 200, 100)
val fruits = List("apple", "pear")
fruits.map(_.toUpperCase) // List(APPLE, PEAR)
fruits.flatMap(_.toUpperCase) // List(A, P, P, L, E, P, E, A, R)
val nums = List(10, 5, 8, 1, 7)
nums.sorted // List(1, 5, 7, 8, 10)
nums.sortWith(_ < _) // List(1, 5, 7, 8, 10)
nums.sortWith(_ > _) // List(10, 8, 7, 5, 1)
8) Встроенные “best practices”
Идиомы Scala поощряют “best practices” во многих ситуациях. Для неизменяемости рекомендуется создавать неизменяемые val переменные:
val a = 1 // неизменяемая переменная
Вам также рекомендуется использовать неизменяемые классы коллекций, такие как List
и Map
:
val b = List(1,2,3) // List неизменяем
val c = Map(1 -> "one") // Map неизменяема
Case классы в первую очередь предназначены для использования в моделировании предметной области, и их параметры также неизменяемы:
case class Person(name: String)
val p = Person("Michael Scott")
p.name // Michael Scott
p.name = "Joe" // compiler error (переназначение val name)
Как показано в предыдущем разделе, классы коллекций Scala поддерживают функции высшего порядка, и вы можете передавать в них методы (не показаны) и анонимные функции:
a.dropWhile(_ < 25)
a.filter(_ < 25)
a.takeWhile(_ < 30)
a.filter(_ < 30).map(_ * 10)
nums.sortWith(_ < _)
nums.sortWith(_ > _)
Выражения match
позволяют использовать сопоставление с образцом, и они действительно являются выражениями, которые возвращают значения:
val numAsString = i match {
case 1 | 3 | 5 | 7 | 9 => "odd"
case 2 | 4 | 6 | 8 | 10 => "even"
case _ => "too big"
}
val numAsString = i match
case 1 | 3 | 5 | 7 | 9 => "odd"
case 2 | 4 | 6 | 8 | 10 => "even"
case _ => "too big"
Поскольку они могут возвращать значения, их часто используют в качестве тела метода:
def isTruthy(a: Matchable) = a match {
case 0 | "" => false
case _ => true
}
def isTruthy(a: Matchable) = a match
case 0 | "" => false
case _ => true
9) Библиотеки экосистемы
Библиотеки Scala для функционального программирования, такие как Cats и Zio, являются передовыми библиотеками в сообществе ФП. Об этих библиотеках можно сказать все модные словечки, такие как высокопроизводительная, типобезопасная, параллельная, асинхронная, ресурсобезопасная, тестируемая, функциональная, модульная, бинарно-совместимая, эффективная, эффектная и т.д.
Мы могли бы перечислить здесь сотни библиотек, но, к счастью, все они перечислены в другом месте: подробности см. в списке “Awesome Scala”.
10) Сильная система типов
В Scala есть сильная система типов, и она была еще больше улучшена в Scala 3. Цели Scala 3 были определены на раннем этапе, и к ним относятся:
- Упрощение
- Устранение несоответствий
- Безопасность
- Эргономика
- Производительность
Упрощение достигается за счет десятков измененных и удаленных функций.
Например, изменения перегруженного ключевого слова implicit
в Scala 2 на термины given
и using
в Scala 3 делает язык более понятным, особенно для начинающих разработчиков.
Устранение несоответствий связано с десятками удаленных функций, измененных функций, и добавленных функций в Scala 3. Некоторые из наиболее важных функций в этой категории:
- Типы пересечения
- Типы объединения
- Неявные функциональные типы
- Зависимые функциональные типы
- Параметры трейтов
- Generic кортежи
Безопасность связана с несколькими новыми и измененными функциями:
- Мультиверсальное равенство
- Ограничение неявных преобразований
- Null безопасность
- Безопасная инициализация
Хорошими примерами эргономики являются перечисления и методы расширения, добавленные в Scala 3 довольно удобочитаемым образом:
// перечисления
enum Color:
case Red, Green, Blue
// методы расширения
extension (c: Circle)
def circumference: Double = c.radius * math.Pi * 2
def diameter: Double = c.radius * 2
def area: Double = math.Pi * c.radius * c.radius
Производительность относится к нескольким областям. Одним из них являются непрозрачные типы. В Scala 2 было несколько попыток создать решения, соответствующие практике проектирования, управляемого предметной областью (DDD), когда значениям присваивались более осмысленные типы. Эти попытки включали:
- Псевдонимы типов
- Классы значений
- Case классы
К сожалению, у всех этих подходов были недостатки, как описано в SIP непрозрачных типов. И наоборот, цель непрозрачных типов, как описано в этом SIP, заключается в том, что “операции с этими типами-оболочками не должны создавать дополнительных накладных расходов во время выполнения, но при этом обеспечивать безопасное использование типов во время компиляции”.
Дополнительные сведения о системе типов см. в справочной документации.
Другие замечательные функции
Scala обладает множеством замечательных функций, и выбор Топ-10 может быть субъективным. Несколько опросов показали, что разные группы разработчиков ценят разные функции. Надеемся, вы откроете для себя больше замечательных возможностей Scala по мере использования языка.
Contributors to this page:
Contents
- Введение
- Возможности Scala
- Почему Scala 3?
- Почувствуй Scala
- Пример 'Hello, World!'
- REPL
- Переменные и типы данных
- Структуры управления
- Моделирование данных
- Методы
- Функции первого класса
- Одноэлементные объекты
- Коллекции
- Контекстные абстракции
- Верхнеуровневые определения
- Обзор
- Первый взгляд на типы
- Интерполяция строк
- Структуры управления
- Моделирование предметной области
- Инструменты
- Моделирование ООП
- Моделирование ФП
- Методы
- Особенности методов
- Main методы в Scala 3
- Обзор
- Функции
- Анонимные функции
- Параметры функции
- Eta расширение
- Функции высшего порядка
- Собственный map
- Создание метода, возвращающего функцию
- Обзор
- Пакеты и импорт
- Коллекции в Scala
- Типы коллекций
- Методы в коллекциях
- Обзор
- Функциональное программирование
- Что такое функциональное программирование?
- Неизменяемые значения
- Чистые функции
- Функции — это значения
- Функциональная обработка ошибок
- Обзор
- Типы и система типов
- Определение типов
- Параметризованные типы
- Пересечение типов
- Объединение типов
- Алгебраические типы данных
- Вариантность
- Непрозрачные типы
- Структурные типы
- Зависимые типы функций
- Другие типы
- Контекстные абстракции
- Методы расширения
- Параметры контекста
- Контекстные границы
- Given импорты
- Классы типов
- Многостороннее равенство
- Неявное преобразование типов
- Обзор
- Параллелизм
- Scala утилиты
- Сборка и тестирование проектов Scala с помощью Sbt
- Рабочие листы
- Взаимодействие с Java