Что такое 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 декларативнее, модульнее и удобнее для поддержки, особенно в крупном коде с доменно-ориентированными типами.