Scala 3 — Book

Возможности Scala

Language

Название Scala происходит от слова scalable, и в соответствии с этим названием язык Scala используется для поддержки загруженных веб-сайтов и анализа огромных наборов данных. В этом разделе представлены функции, которые делают Scala масштабируемым языком. Эти функции разделены на три раздела:

  • Функции высокоуровневого языка программирования
  • Функции низкоуровневого языка программирования
  • Особенности экосистемы Scala

Высокоуровневые функции

Глядя на Scala с пресловутого “вида с высоты 30 000 фунтов”, вы можете сделать о нем следующие утверждения:

  • Это высокоуровневый язык программирования
  • Он имеет краткий, читаемый синтаксис
  • Он статически типизирован (но кажется динамичным)
  • Имеет выразительную систему типов
  • Это язык функционального программирования (ФП)
  • Это язык объектно-ориентированного программирования (ООП)
  • Он поддерживает слияние ФП и ООП
  • Контекстные абстракции обеспечивают понятный способ реализации вывода терминов (term inference)
  • Он работает на JVM (и в браузере)
  • Беспрепятственно взаимодействует с Java кодом
  • Он используется для серверных приложений (включая микросервисы), приложений для работы с большими данными, а также может использоваться в браузере с помощью Scala.js

Эти функции кратко рассматриваются в следующих разделах.

Высокоуровневый язык

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

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

import scala.collection.mutable.ListBuffer

def double(ints: List[Int]): List[Int] = {
  val buffer = new ListBuffer[Int]()
  for (i <- ints) {
    buffer += i * 2
  }
  buffer.toList
}

val oldNumbers = List(1, 2, 3)
val newNumbers = double(oldNumbers)
import scala.collection.mutable.ListBuffer

def double(ints: List[Int]): List[Int] =
  val buffer = new ListBuffer[Int]()
  for i <- ints do
    buffer += i * 2
  buffer.toList

val oldNumbers = List(1, 2, 3)
val newNumbers = double(oldNumbers)

Этот код шаг за шагом указывает компилятору, что делать. Вместо этого мы пишем высокоуровневый функциональный код, используя функции высшего порядка и лямбда-выражения, подобные этому, для вычисления того же результата:

val newNumbers = oldNumbers.map(_ * 2)

Как видите, этот код намного лаконичнее, его легче читать и легче поддерживать.

Лаконичный синтаксис

Scala имеет краткий, удобочитаемый синтаксис. Например, переменные создаются лаконично, а их типы понятны:

val nums = List(1,2,3)
val p = Person("Martin", "Odersky")

Функции высшего порядка и лямбда-выражения делают код кратким и удобочитаемым:

nums.map(i => i * 2)   // длинная форма
nums.map(_ * 2)        // краткая форма

nums.filter(i => i > 1)
nums.filter(_ > 1)

Трэйты, классы и методы определяются с помощью простого и легкого синтаксиса:

trait Animal {
  def speak(): Unit
}

trait HasTail {
  def wagTail(): Unit
}

class Dog extends Animal with HasTail {
  def speak(): Unit = println("Woof")
  def wagTail(): Unit = println("⎞⎜⎛  ⎞⎜⎛")
}
trait Animal:
  def speak(): Unit

trait HasTail:
  def wagTail(): Unit

class Dog extends Animal, HasTail:
  def speak(): Unit = println("Woof")
  def wagTail(): Unit = println("⎞⎜⎛  ⎞⎜⎛")

Исследования показали, что время, которое разработчик тратит на чтение и написание кода, составляет как минимум 10:1, поэтому важно писать краткий и читабельный код.

Ощущение динамики

Scala — это язык со статической типизацией, но благодаря своим возможностям вывода типов он кажется динамичным. Все эти выражения выглядят как языки с динамической типизацией, такие как Python или Ruby, но это все Scala:

val s = "Hello"
val p = Person("Al", "Pacino")
val sum = nums.reduceLeft(_ + _)
val y = for (i <- nums) yield i * 2
val z = nums
  .filter(_ > 100)
  .filter(_ < 10_000)
  .map(_ * 2)
val s = "Hello"
val p = Person("Al", "Pacino")
val sum = nums.reduceLeft(_ + _)
val y = for i <- nums yield i * 2
val z = nums
  .filter(_ > 100)
  .filter(_ < 10_000)
  .map(_ * 2)

Как утверждает Heather Miller, Scala считается сильным языком со статической типизацией, и вы получаете все преимущества статических типов:

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

Выразительная система типов

Система типов в Scala во время компиляции обеспечивает безопасное и согласованное использование абстракций. В частности, система типов поддерживает:

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

Язык функционального программирования

Scala — это язык функционального программирования (ФП), что означает:

  • Функции — это значения, и их можно передавать, как и любое другое значение
  • Напрямую поддерживаются функции высшего порядка
  • Встроенные лямбда
  • Все в Scala — это выражение, возвращающее значение
  • Синтаксически легко использовать неизменяемые переменные, и их использование приветствуется
  • В стандартной библиотеке языка содержится множество неизменяемых классов коллекций
  • Эти классы коллекций поставляются с десятками функциональных методов: они не изменяют коллекцию, вместо этого возвращая обновленную копию данных

Объектно-ориентированный язык

Scala — это язык объектно-ориентированного программирования (ООП). Каждое значение — это экземпляр класса, а каждый “оператор” — это метод.

В Scala все типы наследуются от класса верхнего уровня Any, чьими непосредственными дочерними элементами являются AnyVal (типы значений, такие как Int и Boolean) и AnyRef (ссылочные типы, как в Java). Это означает, что различие в Java между примитивными и упакованными типами (например, int против Integer) отсутствует в Scala. Упаковка и распаковка полностью прозрачны для пользователя.

Поддерживает слияние ФП/ООП

Суть Scala заключается в слиянии функционального программирования и объектно-ориентированного программирования в типизированной среде:

  • Функции для логики
  • Объекты для модульности

Как заявил Мартин Одерски, “Scala был разработан, чтобы показать, что слияние функционального и объектно-ориентированного программирования возможно и практично”.

Вывод терминов стал более понятным

После Haskell Scala был вторым популярным языком, в котором была некоторая форма неявных (implicits) выражений. В Scala 3 эти концепции были полностью переосмыслены и реализованы более четко.

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

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

Scala 3 делает этот процесс более понятным, чем когда-либо прежде. О контекстных абстракциях можно прочесть в Справочной документации.

Клиент & сервер

Код Scala работает на виртуальной машине Java (JVM), поэтому вы получаете все ее преимущества:

  • Безопасность
  • Производительность
  • Управление памятью
  • Портативность и независимость от платформы
  • Возможность использовать множество существующих Java и JVM библиотек

Помимо работы на JVM, Scala также работает в браузере с помощью Scala.js (и сторонних инструментов с открытым исходным кодом для интеграции популярных библиотек JavaScript), а собственные исполняемые файлы могут быть созданы с помощью Scala Native и GraalVM.

Беспрепятственное взаимодействие с Java

Вы можете использовать Java классы и библиотеки в своих приложениях Scala, а также код Scala в приложениях Java. Что касается второго пункта, большие библиотеки, такие как Akka и Play Framework написаны на Scala и могут использоваться в приложениях Java.

Что касается первого пункта, классы и библиотеки Java используются в приложениях Scala каждый день. Например, в Scala вы можете читать файлы с помощью BufferedReader и FileReader из Java:

import java.io.*
val br = BufferedReader(FileReader(filename))
// чтение файла в `br` ...

Использование Java-кода в Scala, как правило, не вызывает затруднений.

В Scala также можно использовать коллекции Java, и если вы хотите использовать с ними богатый набор методов классов коллекций Scala, то можете преобразовать их с помощью всего нескольких строк кода:

import scala.jdk.CollectionConverters.*
val scalaList: Seq[Integer] = JavaClass.getJavaList().asScala.toSeq

Богатство библиотек

Как будет видно в третьем разделе этой страницы, библиотеки и фреймворки Scala, подобные нижеследующим, были написаны для поддержки загруженных веб-сайтов и работы с огромными наборами данных:

  1. Play Framework — это легкая, без сохранения состояния, удобная для web, удобная для разработчиков архитектура для создания масштабируемых приложений
  2. Apache Spark — это унифицированный аналитический механизм для обработки больших данных со встроенными модулями для потоковой передачи, SQL, машинного обучения и обработки графиков

В списке Awesome Scala представлены десятки дополнительных инструментов с открытым исходным кодом, созданных разработчиками для создания приложений Scala.

В дополнение к программированию на стороне сервера, Scala.js представляет собой строго типизированную замену для написания JavaScript со сторонними библиотеками с открытым исходным кодом, которые включают инструменты для интеграции с библиотекой Facebook React, jQuery и т.д.

Функции низкоуровневого языка

Хотя в предыдущем разделе были рассмотрены высокоуровневые функции Scala, интересно отметить, что на высоком уровне вы можете делать одни и те же утверждения как о Scala 2, так и о Scala 3. Десять лет назад Scala начиналась с прочного фундамента желаемых функций, и вы увидите в этом разделе, что в Scala 3 эти преимущества были улучшены.

С точки зрения деталей “на уровне моря” — то есть функций языка, которые программисты используют каждый день — Scala 3 имеет значительные преимущества по сравнению со Scala 2:

  • Возможность более лаконично создавать алгебраические типы данных (ADT) с перечислениями
  • Еще более лаконичный и читаемый синтаксис:
    • Синтаксис “тихой” структуры управления легче читать
    • Опциональные фигурные скобки
      • Меньшее количество символов в коде создает меньше визуального шума, что упрощает его чтение
    • Ключевое слово new обычно больше не требуется при создании экземпляров класса
    • Формальность объектов пакета была заменена более простыми определениями “верхнего уровня”
  • Более понятная грамматика:
    • Несколько различных вариантов использования ключевого слова implicit были удалены; это использование заменено более очевидными ключевыми словами, такими как given, using, и extension, фокусирующихся на намерении, а не механизме (подробности см. в разделе Givens)
    • Методы расширения заменяют неявные классы более понятным и простым механизмом
    • Добавление модификатора open для классов заставляет разработчика намеренно объявить, что класс открыт для модификации, тем самым ограничивая специальные расширения кодовой базы
    • Многостороннее равенство исключает бессмысленные сравнения с == и != (т.е. попытки сравнить Person с Planet)
    • Гораздо проще реализуются макросы
    • Объединение и пересечение предлагают гибкий способ моделирования типов
    • Параметры трейтов заменяют и упрощают ранние инициализаторы
    • Псевдонимы непрозрачных типов заменяют большинство случаев использования классов значений, гарантируя при этом отсутствие упаковки
    • Export предложения обеспечивают простой и общий способ выражения агрегации, который может заменить предыдущий шаблон фасада объектов пакета, наследуемых от классов
    • Синтаксис procedure был удален, а синтаксис varargs - изменен, чтобы сделать язык более согласованным
    • @infix аннотация делает очевидным желаемое применение метода
    • Аннотация метода @targetName определяет альтернативное имя метода, улучшая совместимость с Java и позволяя указывать псевдонимы для символических операторов

Демонстрация всех этих функций заняла бы слишком много места, но перейдите по ссылкам в пунктах выше, чтобы увидеть эти функции в действии. Все эти функции подробно обсуждаются на страницах New, Changed и Dropped функций в обзорной документации.

Экосистема Scala

У Scala динамичная экосистема с библиотеками и фреймворками под любые требования. Список “Awesome Scala” содержит список сотен проектов с открытым исходным кодом, доступных разработчикам Scala, а Scaladex предоставляет доступный для поиска индекс библиотек Scala. Некоторые из наиболее известных библиотек перечислены ниже.

Web разработка

  • Play Framework следует модели Ruby on Rails, чтобы стать легкой, не сохраняющей состояния, удобной для разработчиков и web архитектурой для высокомасштабируемых приложений
  • Scalatra — небольшой высокопроизводительный асинхронный web framework, вдохновленный Sinatra
  • Finatra — это сервисы Scala, построенные на TwitterServer и Finagle
  • Scala.js — это строго типизированная замена JavaScript, обеспечивающая более безопасный способ создания надежных интерфейсных web-приложений
  • ScalaJs-React поднимает библиотеку Facebook React на Scala.js и пытается сделать ее максимально безопасной для типов и удобной для Scala

HTTP(S) библиотеки:

JSON библиотеки:

Сериализация:

Наука и анализ данных:

Большие данные

ИИ, машинное обучение

  • BigDL (Распределенная среда глубокого обучения для Apache Spark)
  • TensorFlow Scala

Функциональное программирование & Функциональное реактивное программирование

ФП:

Функциональное реактивное программирование (ФРП):

Инструменты сборки

Подведем итоги

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

Contributors to this page: