Подтвердить что ты не робот

Как вы обогащаете классы стоимости без накладных расходов?

Scala 2.10 вводит классы значений, которые вы указываете, создавая расширение класса AnyVal. Существует много ограничений на классы значений, но одним из их огромных преимуществ является то, что они позволяют методы расширения без штрафа за создание нового класса: если не требуется бокс, например. чтобы поместить класс значения в массив, это просто старый класс плюс набор методов, которые принимают класс в качестве первого параметра. Таким образом,

implicit class Foo(val i: Int) extends AnyVal {
  def +*(j: Int) = i + j*j
}

разворачивается к чему-то, что может быть не дороже, чем писать i + j*j самостоятельно (как только JVM встраивает вызов метода).

К сожалению, одно из ограничений в SIP-15, которое описывает классы значений

  1. Основной тип C не может быть классом значений.

Если у вас есть класс значений, который вы можете использовать, скажем, в качестве способа обеспечения безопасных типов без накладных расходов на бокс (если это вам действительно не нужно):

class Meter(val meters: Double) extends AnyVal {
  def centimeters = meters*100.0                // No longer type-safe
  def +(m: Meter) = new Meter(meters+m.meters)  // Only works with Meter!
}

то есть ли способ обогатить Meter без накладных расходов объекта? Ограничение в SIP-15 предотвращает очевидное

implicit class RichMeter(m: Meter) extends AnyVal { ... }

подход.

4b9b3361

Ответ 1

Чтобы расширить классы значений, вам нужно вернуть базовый тип. Поскольку классы значений необходимы, чтобы их завернутый тип был доступен (val i не только i выше), вы всегда можете это сделать. Вы не можете использовать удобный implicit class ярлык, но вы все равно можете добавить неявное преобразование. Итак, если вы хотите добавить метод - в Meter, вы должны сделать что-то вроде

class RichMeter(val meters: Double) extends AnyVal {
  def -(m: Meter) = new Meter(meters - m.meters)
}
implicit def EnrichMeters(m: Meter) = new RichMeter(m.meters)

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

(Обратите внимание, что вы получите предупреждение, если вы не import language.implicitConversions.)

Приложение: в Scala 2.11+ вы можете сделать val приватным; для случаев, когда это было сделано, вы не сможете использовать этот трюк.