Русский
Русский
English
Статистика
Реклама

Неявный вывод в Scala

Многие начинающие и не очень Scala разработчики принимают implicits как умеренно полезную возможность. Использование обычно ограничивается передачей ExecutionContextво Future. Другие же избегают неявного и считают возможность вредной.

Код же вроде этого вообще многих пугает:

implicit def function(implicit argument: A): B

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

Кратко про implicits

В целом implicits это механизм автоматического дополнения кода при компиляции:

  • для неявных аргументов автоматически подставляется значение

  • для неявных преобразований значение автоматически оборачивается в вызов метода

Я не буду углубляться в эту тему, кому интересно посмотрите об этом видео от создателя языка. Вкратце один этот механизм используется в нескольких разных случаях (и в Scala 3 будет разделен на несколько отдельных возможностей):

  1. Передача неявного контекста (как ExecutionContext)

  2. Неявные преобразования (не рекомендуется к использованию)

  3. Методы-расширения (extension methods, синтаксический сахар по добавлению методов к существующим типам)

  4. Классы типов (type classes) и неявный вывод (implicit resolution)

Классы типов и неявный вывод

Сами по себе классы тыпов не несут какой-то новизны или революции это попросту реализация методов "для" типа, а не "в самом" типе, это как разница между Comparable& Ordering(Comparatorв Java):

Comparable реализуется для добавления возможности сравнения в ООП стиле:

class Person(age: Int) extends Comparable[Person] {  override def compareTo(o: Person): Int = age compareTo o.age}def max[T <: Comparable[T]](xs: Iterable[T]): T = xs.reduce[T] {  case (a, b) if (a compareTo b) < 0 => b  case (a, _) => a}

Orderingреализуется с той же целью, но отдельно от самого типа:

case class Person(age: Int)implicit object ByAgeOrdering extends Ordering[Person] {  override def compare(o1: Person, o2: Person): Int = o1.age compareTo o2.age}def max[T: Ordering](xs: Iterable[T]): T = xs.reduce[T] {  case (a, b) if Ordering[T].lt(a, b) => b  case (a, _) => a}// is syntactic sugar fordef max[T](xs: Iterable[T])(implicit evidence: Ordering[T]): T = ...

Вот и весь класс типов.

Интересным этот механизм делает неявный вывод. Вернемся к непонятному объявлению из начала статьи и добавим к нему разные комбинации:

implicit val value: A = ???implicit def definition: B = ???implicit def conversion(argument: C): D = ???implicit def function(implicit argument: E): F = ???

В чем разница? Исключая conversion, который является неявным преобразованием, почти ни в чем: value, definition& functionмогут быть использованы чтобы подставить значение для неявного аргумента нужного типа. Разница только в способе вычисления: valстатичен, а defвычисляется каждый раз при постановке. Неявный аргумент в такой функции работает как обычно требует неявного значения в скоупе.

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

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

implicit def pairOrder[A: Ordering, B: Ordering]: Ordering[(A, B)] = {  case ((a1, b1), (a2, b2)) if Ordering[A].equiv(a1, a2) => Ordering[B].compare(b1, b2)  case ((a1,  _), (a2,  _)) => Ordering[A].compare(a1, a2)}// again, just syntactic sugar for:implicit def pairOrder[A, B](implicit a: Ordering[A], b: Ordering[B]): Ordering[(A, B)] = ...

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

val values = Seq(  (Person(30), ("A", "A")),  (Person(30), ("A", "B")),  (Person(20), ("A", "C")))max(values) // => (Person(30),(A,B))

У нас был список типа Seq[(Person, (String, String))]и компилятор смог сам подобрать комбинацию функций для построения Orderingдля этого типа:

max(values)(  pairOrder(    ByAgeOrdering,     pairOrder(Ordering.String, Ordering.String)  ))

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

А главное, если компилятору это не удастся вы получите ошибку компиляции, а не времени выполнения и сможете исправить проблему сразу на месте. Хотя конечно в бочке меда не без ложки дегтя если у компилятора не вышло, вы не знаете какого звена в цепочке не хватило дебажить такое не всегда просто.

Надеюсь неявное стало теперь немного более явным.

Источник: habr.com
К списку статей
Опубликовано: 29.03.2021 22:09:33
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Программирование

Scala

Type class

Implicit

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru