Как работает система типов в Scala?

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

Основы типизации

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

Scala также поддерживает вывод типов, что делает синтаксис более лаконичным. Пример:

val x = 42 // тип Int выводится автоматически
val s = "hello" // тип String

Можно явно указать тип:

val x: Int = 42

Примитивные и ссылочные типы

Scala использует типы JVM (Int, Boolean, Double и т. д.), но представляет их как объекты — это работает за счёт автоматической упаковки и распаковки (boxing/unboxing). Например:

val x: Int = 10 // primitive
val list: List\[Int\] // обобщённый тип с примитивами

Наследование и подтипы

Все типы в Scala наследуются от Any. Он делится на:

  • AnyVal: базовый тип для примитивов (Int, Double, Boolean и т.д.)

  • AnyRef: базовый тип для ссылочных объектов (аналог java.lang.Object)

Корневые типы:

  • Any — супертип всех типов

  • Nothing — подтип всех типов (полезен для обозначения ошибки или отсутствия значения)

  • Null — подтип всех ссылочных типов (AnyRef), но не AnyVal

Пример:

val x: Any = 42
val y: Any = "строка"
val z: Nothing = throw new Exception("Ошибка") // допустимо

Универсальные (параметризованные) типы (Generic types)

Scala поддерживает параметрические типы, аналогичные дженерикам в Java, но более мощные:

def first\[A\](list: List\[A\]): A = list.head

Можно описывать классы с параметрами:

class Box\[A\](val value: A)
val box = new Box

Вариантность типов: +T, -T

Scala поддерживает ковариантность, контрвариантность и инвариантность:

  • +T — ковариантность: List[Dog] можно использовать там, где ожидается List[Animal]

  • -T — контрвариантность: используется в функциях, обработчиках

  • T — инвариантность: Box[Dog] не совместим с Box[Animal]

Примеры:

class Animal
class Dog extends Animal
class Cage\[+T\](animal: T)
val dogCage: Cage\[Dog\] = new Cage(new Dog)
val animalCage: Cage\[Animal\] = dogCage // ок
class Handler\[-T\] {
def handle(t: T): Unit = println(t)
}
val animalHandler: Handler\[Animal\] = new Handler\[Animal\]
val dogHandler: Handler\[Dog\] = animalHandler // ок

Типы функций

Функции в Scala — это объекты, реализующие интерфейс FunctionN:

val f: Int => String = \_.toString
val g: (Int, Int) => Int = _ + _

Функция с несколькими аргументами — это кортеж:

(Int, Int) => Int

Типы первого класса (higher-order types)

Scala позволяет использовать высшие типы, т.е. типы, принимающие другие типы как параметры:

trait Functor\[F\[\_\]\] {
def map\[A, B\](fa: F\[A\])(f: A => B): F\[B\]
}

Абстрактные типы

Можно объявлять абстрактные типы в трейтах:

trait Repository {
type Entity
def all(): List\[Entity\]
}

И затем реализовать:

class UserRepo extends Repository {
type Entity = User
def all(): List\[User\] = ...
}

Типовые ограничения (upper/lower bounds)

  • A <: B — A должен быть подтипом B

  • A >: B — A должен быть супертипом B

Пример:

def printAnimal\[A <: Animal\](a: A): Unit = println(a)

Также доступны context bounds и view bounds:

def max\[A: Ordering\](a: A, b: A): A = if (implicitly\[Ordering\[A\]\].gt(a, b)) a else b

Самостоятельные (self) типы

Можно указать, что данный трейт должен быть смешан с другим:

trait A {
def run(): Unit
}
trait B { self: A =>
def execute(): Unit = run()
}

Типы-пересечения и объединения (начиная с Scala 3)

  • Пересечение: A & B

  • Объединение: A | B

В Scala 2 для пересечения использовался with.

Псевдонимы типов

Для сокращения длинных сигнатур:

type UserId = Int
type ErrorOr\[A\] = Either\[String, A\]

Type-level programming

Scala поддерживает сложную типовую метапрограмму, включая:

  • Singleton-тип (val x: 5 = 5)

  • Лямбды на уровне типов (Scala 3)

  • Типы зависимые от значений (x.type)

Типы как параметры и значения

Можно передавать и возвращать типы, использовать TypeTag, ClassTag, Manifest для работы с рефлексией.

Система типов Scala стремится обеспечить типовую безопасность без необходимости указывать все типы явно. Она активно используется для композиции абстракций, обеспечения модульности, повышения читаемости и построения безопасных API.