Неявный класс против неявного преобразования в трейт

Я пытаюсь добавить новые функции к существующим типам (чтобы IDE автоматически предлагала соответствующие функции для типов, которые я не могу контролировать, например Future[Option[A]]). Я исследовал как неявные классы, так и неявные преобразования, чтобы добиться этого, и оба они, похоже, предлагают одинаковое поведение.

Есть ли какая-либо эффективная разница между использованием неявного класса:

case class Foo(a: Int)
implicit class EnrichedFoo(foo: Foo) {
  def beep = "boop"
}
Foo(1).beep // "boop"

И используя неявное преобразование:

case class Foo(a: Int)
trait Enriched {
  def beep: String
}
implicit def fooToEnriched(foo: Foo) = new Enriched {
  def beep = "boop"
}
Foo(1).beep // "boop"

Я предполагаю, что одно отличие здесь может заключаться в том, что в первом примере вместо трейта создается одноразовый класс, но я мог бы легко адаптировать неявный класс для расширения абстрактного трейта, например:

case class Foo(a: Int)
trait Enriched {
  def beep: String
}
implicit class EnrichedFoo(foo: Foo) extends Enriched {
  def beep = "boop"
}
Foo(1).beep // "boop"

person Mark Pierotti    schedule 05.04.2016    source источник


Ответы (3)


Насколько я знаю, они почти одинаковые. Правила области видимости также в равной степени применяются к обоим.

На мой взгляд, я бы использовал implicit classes для вашей ситуации. Вероятно, они были созданы именно для чего-то подобного.

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

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

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

Вы даже можете увидеть, как он обесахаривает implicit classes. Следующее:

implicit class RichInt(n: Int) extends Ordered[Int] {
   def min(m: Int): Int = if (n <= m) n else m
   ...
}

будет обессахариваться в:

class RichInt(n: Int) extends Ordered[Int] {
  def min(m: Int): Int = if (n <= m) n else m
  ...
}
implicit final def RichInt(n: Int): RichInt = new RichInt(n)
person Luka Jacobowitz    schedule 05.04.2016
comment
Следует упомянуть еще одну вещь: если вы хотите неявно преобразовать объект типа A в объект типа B, где B — это final class , единственным вариантом будет implicit def. - person Adowrath; 16.05.2017

Ну для меня это вопрос предпочтений. На самом деле implicit classes появился, чтобы облегчить создание классов, которые предоставляют методы расширения для другого типа. Тем не менее, неявные классы добавляют большую ценность value classes.

person Som Bhattacharyya    schedule 05.04.2016

Чтобы добавить ответ Луки Якобовица: неявные классы в основном являются расширениями. Неявное преобразование используется, чтобы сообщить компилятору, что его можно рассматривать как что-то с расширением.

Звучит почти так же. Две интересные вещи из неявного преобразования имеют некоторую разницу:

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

Во-вторых: термин «преобразование» типа может сбивать с толку:

Неявные преобразования применяются в двух ситуациях: Если выражение e имеет тип S, а S не соответствует ожидаемому типу выражения T. [Или:] При выборе em с e типа S, если селектор m не обозначает член С.

case class Foo(a: Int)
trait Enriched {
  def beep: String
}
implicit def fooToEnriched(foo: Foo) = new Enriched {
  def beep = "boop"
}

Foo(1) match {
  case _:Enriched => println("is an Enriched")
  case _:Foo => println("no, was a Foo")
}
// no, was a Foo

но его можно рассматривать как обогащенный...

val enriched: Enriched = Foo(2)
enriched match {
  case _:Enriched => println("is an Enriched")
  case _:Foo => println("no, was a Foo")
}
// is an Enriched
// plus unreachable code warning: case _:Foo => println("no, was a Foo")
person AyJayKay    schedule 05.06.2019