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

Классы типов и неявные параметры

Научитесь использовать классы типов для специального полиморфизма и применять систему неявных параметров Scala для создания мощных абстракций.

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

Объяснение специального полиморфизма

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

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

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

Типовой класс — это шаблон проектирования, который позволяет добавлять новое поведение существующим типам, не изменяя их и не используя наследование.

  • Он определяет контракт (признак) для определённого поведения.
  • Он предоставляет «экземпляры» (объекты), реализующие этот контракт для разных типов.
  • Он использует механизм неявных значений Scala, чтобы автоматически сделать нужное поведение доступным в текущей области видимости.

Определение признака типового класса

Сначала определим признак, описывающий нужное нам поведение. Этот признак принимает параметр типа A, который представляет тип, к которому будет применяться наше поведение.

Создадим типовой класс Printable, умеющий преобразовывать любой тип в удобочитаемую строку.

package com.coddykit

trait Printable[A] {
  def format(value: A): String
}

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

Теперь нужно сообщить Scala, как сделать конкретные типы Printable. Для этого создадим неявные объекты (или значения), расширяющие наш признак Printable для каждого типа.

Они называются экземплярами типового класса.

package com.coddykit

trait Printable[A] {
  def format(value: A): String
}

object PrintableInstances {
  implicit object StringPrintable extends Printable[String] {
    def format(value: String): String = s"'${value}'"
  }

  implicit object IntPrintable extends Printable[Int] {
    def format(value: Int): String = s"${value}i"
  }
}

Использование типовых классов с неявными значениями

Чтобы использовать наш типовой класс, определим функцию, принимающую неявный параметр типового класса. При вызове этой функции компилятор Scala автоматически найдёт доступный неявный экземпляр Printable[A].

Если он его найдёт, то передаст его функции!

package com.coddykit

trait Printable[A] {
  def format(value: A): String
}

object PrintableInstances {
  implicit object StringPrintable extends Printable[String] {
    def format(value: String): String = s"'${value}'"
  }

  implicit object IntPrintable extends Printable[Int] {
    def format(value: Int): String = s"${value}i"
  }
}

object PrintableSyntax {
  def print[A](value: A)(implicit p: Printable[A]): Unit = {
    println(p.format(value))
  }
}

object Main {
  import PrintableInstances._
  import PrintableSyntax._

  def main(args: Array[String]): Unit = {
    print("hello")
    print(123)
  }
}

Ограничения контекста: более простой синтаксис

Scala предлагает сокращённую запись для неявных параметров — ограничение контекста. Вместо (implicit p: Printable[A]) можно написать [A: Printable].

Так код становится понятнее, особенно если у вас несколько неявных параметров.

package com.coddykit

trait Printable[A] {
  def format(value: A): String
}

object PrintableInstances {
  implicit object StringPrintable extends Printable[String] {
    def format(value: String): String = s"'${value}'"
  }

  implicit object IntPrintable extends Printable[Int] {
    def format(value: Int): String = s"${value}i"
  }
}

object PrintableSyntax {
  // Using context bound: [A: Printable] is shorthand for (implicit p: Printable[A])
  def print[A: Printable](value: A): Unit = {
    // To access the implicit instance, use implicitly[Printable[A]]
    val p = implicitly[Printable[A]]
    println(p.format(value))
  }
}

object Main {
  import PrintableInstances._
  import PrintableSyntax._

  def main(args: Array[String]): Unit = {
    print("world")
    print(456)
  }
}

Расширение для пользовательских типов

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

Сделаем наш класс вариантов Person типом Printable.

package com.coddykit

trait Printable[A] {
  def format(value: A): String
}

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

object PrintableInstances {
  implicit object StringPrintable extends Printable[String] {
    def format(value: String): String = s"'${value}'"
  }

  implicit object IntPrintable extends Printable[Int] {
    def format(value: Int): String = s"${value}i"
  }

  // New instance for our custom Person type
  implicit object PersonPrintable extends Printable[Person] {
    def format(person: Person): String = 
      s"Person(name: ${person.name}, age: ${person.age})"
  }
}

object PrintableSyntax {
  def print[A: Printable](value: A): Unit = {
    val p = implicitly[Printable[A]]
    println(p.format(value))
  }
}

object Main {
  import PrintableInstances._
  import PrintableSyntax._

  def main(args: Array[String]): Unit = {
    val alice = Person("Alice", 30)
    print(alice)
  }
}

Неявные значения: не только типовые классы

Хотя типовые классы — основной вариант использования, ключевое слово implicit в Scala представляет собой более общий механизм. Его можно использовать для:

  • Неявных параметров: как в типовых классах, чтобы автоматически передавать значения.
  • Неявных преобразований: для автоматического преобразования одного типа в другой (используйте с осторожностью, поскольку они могут затруднить понимание кода).
  • Неявных значений: для задания значений по умолчанию для определённых типов, например контекстов выполнения.

Основная идея заключается в том, что компилятор ищет подходящие неявные определения в текущей области видимости.

Преимущества типовых классов

Типовые классы дают несколько преимуществ:

  • Расширяемость: добавление нового поведения существующим типам без их изменения.
  • Разделение ответственности: отделение определения типа от его поведения.
  • Специальный полиморфизм: функции могут работать с любым типом, предоставляющим требуемое поведение.
  • Удобство тестирования: более простое тестирование поведения изолированно.
  • Отсутствие накладных расходов наследования: позволяет избежать сложностей глубоких иерархий наследования.

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

Рассмотрим типовой класс Printable и его экземпляры из нашего урока. Что произойдёт при вызове print(10.5), если предположить, что экземпляра DoublePrintable не существует?

Итоги и следующие шаги

Вы освоили типовые классы и мощную систему неявных значений Scala!

  • Типовые классы поддерживают специальный полиморфизм, позволяя определять поведение типов без наследования.
  • Они состоят из признака (контракта) и неявных объектов/значений (экземпляров).
  • Неявные параметры и ограничения контекста позволяют функциям автоматически находить и использовать эти экземпляры.

Типовые классы — основа функционального программирования в Scala. Они широко используются в библиотеках, таких как Cats и ZIO, для создания гибких и надёжных приложений. Продолжайте практиковаться, чтобы закрепить понимание!

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

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

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

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

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

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

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

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

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

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

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

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

  1. Обобщения и параметры типов
  2. Вариантность: ковариантность и контравариантность
  3. Классы типов и неявные параметры
← Назад к Scala for Backend Engineering & Functional Programming