Типы match

Тип match сводится к одной из его правых частей, в зависимости от проверяемого типа. Например:

type Elem[X] = X match
  case String => Char
  case Array[t] => t
  case Iterable[t] => t

Матчинг определяет тип, который сокращается следующим образом:

Elem[String]       =:=  Char
Elem[Array[Int]]   =:=  Int
Elem[List[Float]]  =:=  Float
Elem[Nil.type]     =:=  Nothing

Здесь под =:= подразумевается, что левая и правая стороны являются взаимными подтипами друг друга.

В общем случае тип match имеет вид:

S match { P1 => T1 ... Pn => Tn }

где S, T1, ..., Tn - типы, а P1, ..., Pn - шаблоны типов. Переменные типа в шаблонах, как обычно, начинаются со строчной буквы.

Типы match могут составлять часть определений рекурсивных типов. Пример:

type LeafElem[X] = X match
  case String => Char
  case Array[t] => LeafElem[t]
  case Iterable[t] => LeafElem[t]
  case AnyVal => X

Определения рекурсивного типа match также могут иметь верхнюю границу, например:

type Concat[Xs <: Tuple, +Ys <: Tuple] <: Tuple = Xs match
  case EmptyTuple => Ys
  case x *: xs => x *: Concat[xs, Ys]

В этом определении известно, что каждый экземпляр Concat[A, B], редуцированный или нет, является подтипом Tuple. Это необходимо для проверки типа рекурсивного вызова x *: Concat[xs, Ys], поскольку *: требует в качестве правого операнда Tuple.

Зависимая типизация

Типы match можно использовать для определения методов с зависимыми типами. Например:

def leafElem[X](x: X): LeafElem[X] = x match
  case x: String      => x.charAt(0)
  case x: Array[t]    => leafElem(x(0))
  case x: Iterable[t] => leafElem(x.head)
  case x: AnyVal      => x

Результат:

leafElem(Array(List("abc")))
// res0: Char = 'a'

Этот специальный режим ввода выражений match используется только при выполнении следующих условий:

Termination

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

Поскольку редукция связана с созданием подтипов, есть механизм обнаружения циклов. В результате следующее выдаст разумное сообщение об ошибке:

type L[X] = X match
  case Int => L[X]

def g[X]: L[X] = ???
|  val x: Int = g[Int]
|                  ^
|  Recursion limit exceeded.
|  Maybe there is an illegal cyclic reference?
|  If that's not the case, you could also try to
|  increase the stacksize using the -Xss JVM option.
|  A recurring operation is (inner to outer):
|  
|    subtype LazyRef(Test.L[Int]) <:< Int

Внутри компилятор Scala обнаруживает такие циклы, превращая выбранные переполнения стека в ошибки типов. Если во время создания подтипа происходит переполнение стека, исключение будет перехвачено и преобразовано в ошибку времени компиляции, указывающую на трассировку тестов подтипа, вызвавшую переполнение, без отображения полной трассировки стека.

Match Types Variance

Все позиции типа в типе match (проверяемые, шаблоны, тела) считаются инвариантными.


Ссылки: