scalabook

Форк
0
163 строки · 8.0 Кб

Алгебраические типы данных (ADT)

Алгебраические типы данных (ADT) могут быть созданы с помощью конструкции enum

.

Концепция enum

является достаточно общей, чтобы также поддерживать алгебраические типы данных (ADT) и их обобщенную версию (GADT). Вот пример, показывающий, как тип Option
может быть представлен в виде ADT:

enum Option[+T]:
case Some(x: T)
case None

В этом примере создается Option

enum
с параметром ковариантного типа T
, состоящим из двух вариантов: Some
и None
. Some
параметризуются параметром значения x
; это сокращение для написания case класса, который расширяет Option
. Поскольку None
не параметризован, он обрабатывается как обычное значение enum
.

Предложения extends

, которые были опущены в предыдущем примере, также могут быть указаны явно:

enum Option[+T]:
case Some(x: T) extends Option[T]
case None extends Option[Nothing]

Как и в случае с обычными значениями enum

, case
enum
определяются в сопутствующем объекте перечисления, поэтому они называются Option.Some
и Option.None
(если только определения не "вытягиваются" при импорте):

Option.Some("hello")
// res0: Option[String] = Some(x = "hello")
Option.None
// res1: Option[Nothing] = None

Обратите внимание, что тип приведенных выше выражений всегда Option

. Как правило, тип case enum будет расширен до базового типа enum, если не ожидается более конкретный тип. Это тонкая разница по сравнению с обычными case class-ами. Классы, составляющие case enum, существуют, и их можно использовать, либо создав их непосредственно с помощью new
, либо явно указав ожидаемый тип.

new Option.Some(2)
// res2: Some[Int] = Some(x = 2)
val x: Option.Some[Int] = Option.Some(3)
// x: Some[Int] = Some(x = 3)

Как и в других случаях использования перечисления, АТД могут определять дополнительные методы. Например, вот снова Option

с методом isDefined
и конструктором Option(...)
в сопутствующем объекте:

enum Option[+T]:
case Some(x: T)
case None
def isDefined: Boolean = this match
case None => false
case Some(_) => true
object Option:
def apply[T >: Null](x: T): Option[T] =
if (x == null) None else Some(x)

Перечисления и ADT используют одну и ту же синтаксическую конструкцию, поэтому их можно рассматривать просто как два конца спектра, и вполне возможно создавать гибриды. Например, приведенный ниже код дает реализацию Color

либо с тремя значениями перечисления, либо с параметризованным case, который принимает значение RGB:

enum Color(val rgb: Int):
case Red extends Color(0xFF0000)
case Green extends Color(0x00FF00)
case Blue extends Color(0x0000FF)
case Mix(mix: Int) extends Color(mix)
Рекурсивные перечисления

До сих пор все перечисления состояли только из различных вариантов значений или case class-ов. Перечисления также могут быть рекурсивными, как показано в приведенном ниже примере кодирования натуральных чисел:

enum Nat:
case Zero
case Succ(n: Nat)

Например, значение Succ(Succ(Zero))

представляет число 2
в унарной кодировке. Очень похожим образом могут быть определены списки:

enum List[+A]:
case Nil
case Cons(head: A, tail: List[A])

Обобщенные алгебраические типы данных (GADT)

Приведенная выше нотация для перечислений очень лаконична и служит идеальной отправной точкой для моделирования типов данных. Также возможно выразить гораздо более мощные типы: обобщенные алгебраические типы данных (GADTs).

Вот пример GADT, где параметр типа (T

) определяет содержимое, хранящееся в поле:

enum Box[T](contents: T):
case IntBox(n: Int) extends Box[Int](n)
case BoolBox(b: Boolean) extends Box[Boolean](b)

Сопоставление с шаблоном в конкретном конструкторе (IntBox

или BoolBox
) восстанавливает информацию о типе:

def extract[T](b: Box[T]): T = b match
case IntBox(n) => n + 1
case BoolBox(b) => !b

Безопасно возвращать Int

только в первом случае, так как из сопоставления с шаблоном известно, что b
был IntBox
.

Дешугаризация перечислений

Концептуально перечисления можно рассматривать как определение sealed

класса вместе с сопутствующим объектом. Посмотрим на дешугаризацию перечисления Color
:

sealed abstract class Color(val rgb: Int) extends scala.reflect.Enum
object Color:
case object Red extends Color(0xFF0000) { def ordinal = 0 }
case object Green extends Color(0x00FF00) { def ordinal = 1 }
case object Blue extends Color(0x0000FF) { def ordinal = 2 }
case class Mix(mix: Int) extends Color(mix) { def ordinal = 3 }
def fromOrdinal(ordinal: Int): Color = ordinal match
case 0 => Red
case 1 => Green
case 2 => Blue
case _ => throw new NoSuchElementException(ordinal.toString)

Вышеописанная дешугаризация упрощена, детали доступны по ссылке.

Хотя перечисления можно кодировать вручную с помощью других конструкций, использование enum

является более кратким, а также включает несколько дополнительных утилит (таких как метод fromOrdinal
).


Ссылки:

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.