0Pricing
Scala for Backend Engineering & Functional Programming · Урок

Шаблон классов типов

Аd hoc-полиморфизм

«Шаблон классов типов» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Что такое класс типов?

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

Этот подход появился в Haskell и широко используется в библиотеках Scala, например Cats.

Какую проблему он решает

Наследование встраивает поведение в тип во время его определения. Но часто Вы не можете изменить тип, потому что он находится в библиотеке, или хотите разное поведение в разных контекстах. Классы типов позволяют присоединять поведение внешним образом.

Шаг 1: определение трейта

Класс типов — это параметризованный типом trait. Здесь Show[A] описывает способ отображения любого A в String.

trait Show[A] {
  def show(value: A): String
}

@main def run(): Unit = {
  println("Show trait defined")
}

Шаг 2: предоставление экземпляров

Для каждого конкретного типа, который Вы хотите поддерживать, создайте instance трейта. Это и есть экземпляры класса типов.

trait Show[A] { def show(value: A): String }

object Main {
  val intShow: Show[Int] = (v: Int) => s"Int($v)"
  val strShow: Show[String] = (v: String) => s"Str($v)"

  def main(args: Array[String]): Unit = {
    println(intShow.show(7))
    println(strShow.show("hi"))
  }
}

Шаг 3: использование экземпляра

Функция принимает экземпляр в качестве параметра. Одна и та же функция render работает с любым типом, для которого есть экземпляр Show.

trait Show[A] { def show(value: A): String }

object Main {
  def render[A](value: A, s: Show[A]): String = s.show(value)

  val intShow: Show[Int] = (v: Int) => s"<$v>"

  def main(args: Array[String]): Unit = {
    println(render(99, intShow))
  }
}

Неявная передача

Передавать экземпляры вручную утомительно. Если пометить экземпляр и параметр как implicit (или использовать using в Scala 3), компилятор сможет подставлять экземпляр автоматически.

trait Show[A] { def show(value: A): String }

object Main {
  implicit val intShow: Show[Int] = (v: Int) => s"<$v>"

  def render[A](value: A)(implicit s: Show[A]): String = s.show(value)

  def main(args: Array[String]): Unit = {
    println(render(42))
  }
}

Непараметрический полиморфизм

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

trait Show[A] { def show(value: A): String }

object Main {
  implicit val intShow: Show[Int] = (v: Int) => s"int:$v"
  implicit val boolShow: Show[Boolean] = (v: Boolean) => s"bool:$v"

  def render[A](value: A)(implicit s: Show[A]): String = s.show(value)

  def main(args: Array[String]): Unit = {
    println(render(5))
    println(render(true))
  }
}

Работа с собственными типами

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

trait Show[A] { def show(value: A): String }

case class User(name: String, age: Int)

object Main {
  implicit val userShow: Show[User] = (u: User) => s"${u.name} (${u.age})"

  def render[A](value: A)(implicit s: Show[A]): String = s.show(value)

  def main(args: Array[String]): Unit = {
    println(render(User("Ada", 36)))
  }
}

Классы типов и интерфейсы

Интерфейс связывает поведение с определением типа, а класс типов отделяет их друг от друга.

  • Вы можете добавить экземпляр класса типов для типа, которым не владеете.
  • Вы можете иметь несколько экземпляров для разных контекстов.
  • Компилятор выбирает поведение в месте вызова.

Три компонента

У каждого класса типов есть три части:

  • Трейт — абстрактная операция.
  • Экземпляры — реализации для конкретных типов.
  • Интерфейс — функции, которым требуется экземпляр.

В следующих уроках Вы рассмотрите каждую из них.

Полный небольшой пример

Объединим все элементы подхода в обобщённой функции, использующей неявный экземпляр.

trait Show[A] { def show(value: A): String }

object Main {
  implicit val intShow: Show[Int] = (v: Int) => s"#$v"
  implicit val strShow: Show[String] = (v: String) => '"' + v + '"'

  def printAll[A](xs: List[A])(implicit s: Show[A]): Unit =
    xs.foreach(x => println(s.show(x)))

  def main(args: Array[String]): Unit = {
    printAll(List(1, 2, 3))
    printAll(List("a", "b"))
  }
}

Быстрая проверка

Проверьте, насколько хорошо Вы поняли подход с классами типов.

Итоги

Вы изучили подход с классами типов:

  • Параметризованный trait описывает операцию.
  • Экземпляры реализуют её для конкретных типов.
  • Параметры implicit позволяют компилятору подставлять экземпляр.
  • Это обеспечивает непараметрический полиморфизм и позволяет расширять типы, которыми Вы не владеете.

Часто задаваемые вопросы

Урок «Шаблон классов типов» бесплатный?

Да — полный текст урока «Шаблон классов типов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Чему я научусь в уроке «Шаблон классов типов»?

Аd hoc-полиморфизм Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?

Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Шаблон классов типов»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?

Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Шаблон классов типов
  2. Определение экземпляров
  3. Распространённые классы типов
  4. Выведение классов типов
← Назад к Scala for Backend Engineering & Functional Programming