Что такое type alias и где он применяется?
В Scala type alias (псевдоним типа) — это способ дать альтернативное, более читаемое или сокращённое имя уже существующему типу. Он не создаёт новый тип, а лишь обозначает новый идентификатор, который указывает на уже существующий тип.
Синтаксис
type ИмяПсевдонима = СуществующийТип
Пример:
type UserId = Int
type UserName = String
Теперь можно использовать UserId и UserName вместо Int и String в сигнатурах методов, переменных, типах параметров и т.п.
Зачем используется type alias
1. Повышение читаемости
Если функция принимает, например, Map[String, List[(String, Int)]], она может быть плохо читаема:
def process(data: Map\[String, List\[(String, Int)\]\]): Unit = ???
Можно улучшить читаемость с помощью псевдонима:
type ScoreList = List\[(String, Int)\]
type UserScores = Map\[String, ScoreList\]
def process(data: UserScores): Unit = ???
Такой подход особенно полезен в API, где читаемость критична.
2. Упрощение длинных типов
Когда приходится часто использовать громоздкие типы (например, функции высших порядков, вложенные generic-структуры), type делает код короче и чище.
Пример с функцией:
type Callback = (Int, String) => Either\[Throwable, Boolean\]
def register(cb: Callback): Unit = ???
3. Снижение дублирования
Если один и тот же тип встречается во многих местах, особенно если он изменится в будущем, замена типа в одном type-определении упростит сопровождение:
type Email = String
case class User(id: Int, email: Email)
case class ContactInfo(primary: Email, secondary: Option\[Email\])
Если потом понадобится заменить Email = String на Email = EmailAddress, это можно сделать централизованно.
4. Инкапсуляция и абстракция
Когда вы хотите абстрагировать тип, скрыв детали его реализации за интерфейсом или трейтами:
trait Repository {
type Entity
def findAll(): List\[Entity\]
}
А в реализации:
class UserRepository extends Repository {
type Entity = User
def findAll(): List\[User\] = ...
}
5. Псевдонимы для параметризованных типов (Generic Types)
Можно создавать псевдонимы для обобщённых структур:
type IntMap\[A\] = Map\[Int, A\]
val data: IntMap\[String\] = Map(1 -> "one", 2 -> "two")
Или для Either:
type ErrorOr\[A\] = Either\[String, A\]
def compute(): ErrorOr\[Int\] = Right(42)
Ограничения и особенности
-
Type alias не создаёт новый тип, а только новое имя для уже существующего. Следовательно, нельзя использовать alias для обеспечения type safety (в отличие от opaque type или value class).
-
Нельзя иметь два разных alias с одинаковой структурой и ожидать, что компилятор будет их различать:
type Meters = Double
type Kilograms = Double
def compute(m: Meters, k: Kilograms): Unit = ??? // но они оба остаются Double
В этом случае может быть полезнее использовать value class или opaque type, чтобы различать логически разные значения одного и того же типа.
Вложенные type alias
Можно объявлять псевдонимы типов внутри объектов или трейтов, особенно в библиотеках, которые хотят скрыть детали реализации или выделить доменные типы:
object Domain {
type UserId = Int
type UserName = String
case class User(id: UserId, name: UserName)
}
Тип как член трейта или класса
Type alias может быть также абстрактным членом, определяемым в наследниках:
trait Algebra {
type A
def run(value: A): Int
}
class StringAlgebra extends Algebra {
type A = String
def run(value: String): Int = value.length
}
Такой подход позволяет использовать path-dependent types, что бывает полезно в модульной архитектуре.
Отличие от opaque type (начиная с Scala 3)
Если в Scala 3 нужно создать реально новый тип, который не совместим с исходным типом, то нужно использовать opaque type, а не type:
opaque type UserId = Int
object UserId {
def apply(id: Int): UserId = id
}
В этом случае UserId будет типобезопасен и не будет совместим с Int.
Примеры использования
// Типизированные алгебры
type QueryResult = Either\[Throwable, String\]
// Тип коллекции
type Row = Map\[String, Any\]
// Тип функции
type Validator\[A\] = A => Either\[String, A\]
// Generic
type IDMap\[A\] = Map\[UUID, A\]
Type alias делает код Scala декларативнее, модульнее и удобнее для поддержки, особенно в крупном коде с доменно-ориентированными типами.