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

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 { ... }

подход.

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 но выполняет сложное смешивание битов), вы можете просто переопределить базовый класс в классе значений, который вы пытаетесь расширить, где бы вам это ни понадобилось.

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

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

Другие вопросы по тегам