Эта страница документа относится к Scala 3 и может охватывать новые концепции, недоступные в Scala 2. Если не указано явно, все примеры кода на этой странице предполагают, что вы используете Scala 3.
Scala 2 содержит более слабую форму структурных типов, основанную на Java reflection,
достигаемую с помощью import scala.language.reflectiveCalls
.
Введение
Некоторые варианты использования, такие как моделирование доступа к базе данных,
более удобны в динамически типизированных языках, чем в статически типизированных языках.
С динамически типизированными языками естественно моделировать строку как запись или объект
и выбирать записи с помощью простых точечных обозначений, например row.columnName
.
Достижение того же результата в статически типизированном языке требует определения класса для каждой возможной строки,
возникающей в результате манипуляций с базой данных, включая строки, возникающие в результате join
и проектирования,
и настройки схемы для сопоставления между строкой и представляющим ее классом.
Это требует большого количества шаблонов,
что заставляет разработчиков менять преимущества статической типизации на более простые схемы,
в которых имена столбцов представляются в виде строк и передаются другим операторам, например row.select("columnName")
.
Этот подход лишен преимуществ статической типизации и все еще не так естественен, как динамически типизируемая версия.
Структурные типы (structural types) помогают в ситуациях, когда желательно поддерживать простую точечную нотацию в динамических контекстах, не теряя преимуществ статической типизации. Они также позволяют разработчикам настраивать, как должны определяться поля и методы.
Пример
Вот пример структурного типа Person
:
class Record(elems: (String, Any)*) extends Selectable:
private val fields = elems.toMap
def selectDynamic(name: String): Any = fields(name)
type Person = Record {
val name: String
val age: Int
}
Тип Person
добавляет уточнение (refinement) к своему родительскому типу Record
, которое определяет поля name
и age
.
Говорится, что уточнение носит структурный (structural) характер,
поскольку name
и age
не определены в родительском типе.
Но тем не менее они существуют как члены класса Person
.
Например, следующая программа напечатала бы "Emma is 42 years old."
:
val person = Record(
"name" -> "Emma",
"age" -> 42
).asInstanceOf[Person]
println(s"${person.name} is ${person.age} years old.")
Родительский тип Record
в этом примере представляет собой универсальный класс,
который может в своем аргументе elems
принимать произвольные записи.
Этот аргумент - последовательность пар ключей типа String
и значений типа Any
.
Когда создается Person
как Record
, необходимо с помощью приведения типов задать,
что запись определяет правильные поля правильных типов.
Сама Record
слишком слабо типизирована, поэтому компилятор не может знать об этом без помощи пользователя.
На практике связь между структурным типом и его базовым общим представлением, скорее всего,
будет выполняться на уровне базы данных и, следовательно, не будет беспокоить конечного пользователя.
Record
расширяет маркер trait scala.Selectable
и определяет метод selectDynamic
,
который сопоставляет имя поля с его значением.
Выбор элемента структурного типа выполняется путем вызова соответствующего метода.
person.name
и person.age
преобразуются компилятором Scala в:
person.selectDynamic("name").asInstanceOf[String]
person.selectDynamic("age").asInstanceOf[Int]
Второй пример
Чтобы закрепить сказанное, вот еще один структурный тип с именем Book
, представляющий книгу, доступную в базе данных:
type Book = Record {
val title: String
val author: String
val year: Int
val rating: Double
}
Как и в случае с Person
, экземпляр Book
создается следующим образом:
val book = Record(
"title" -> "The Catcher in the Rye",
"author" -> "J. D. Salinger",
"year" -> 1951,
"rating" -> 4.5
).asInstanceOf[Book]
Класс Selectable
Помимо selectDynamic
класс Selectable
иногда также определяет метод applyDynamic
,
который можно использовать для замены вызовов функций на вызов структурных элементов.
Таким образом, если a
является экземпляром Selectable
, структурный вызов типа a.f(b, c)
преобразуется в:
a.applyDynamic("f")(b, c)
Contributors to this page:
Contents
- Введение
- Возможности Scala
- Почему Scala 3?
- Почувствуй Scala
- Пример 'Hello, World!'
- REPL
- Переменные и типы данных
- Структуры управления
- Моделирование данных
- Методы
- Функции первого класса
- Одноэлементные объекты
- Коллекции
- Контекстные абстракции
- Верхнеуровневые определения
- Обзор
- Первый взгляд на типы
- Интерполяция строк
- Структуры управления
- Моделирование предметной области
- Инструменты
- Моделирование ООП
- Моделирование ФП
- Методы
- Особенности методов
- Main методы в Scala 3
- Обзор
- Функции
- Анонимные функции
- Параметры функции
- Eta расширение
- Функции высшего порядка
- Собственный map
- Создание метода, возвращающего функцию
- Обзор
- Пакеты и импорт
- Коллекции в Scala
- Типы коллекций
- Методы в коллекциях
- Обзор
- Функциональное программирование
- Что такое функциональное программирование?
- Неизменяемые значения
- Чистые функции
- Функции — это значения
- Функциональная обработка ошибок
- Обзор
- Типы и система типов
- Определение типов
- Параметризованные типы
- Пересечение типов
- Объединение типов
- Алгебраические типы данных
- Вариантность
- Непрозрачные типы
- Структурные типы
- Зависимые типы функций
- Другие типы
- Контекстные абстракции
- Методы расширения
- Параметры контекста
- Контекстные границы
- Given импорты
- Классы типов
- Многостороннее равенство
- Неявное преобразование типов
- Обзор
- Параллелизм
- Scala утилиты
- Сборка и тестирование проектов Scala с помощью Sbt
- Рабочие листы
- Взаимодействие с Java