Как вы обогащаете классы стоимости без накладных расходов?
Scala 2.10 представляет классы значений, которые вы определяете, расширяя свой класс AnyVal
, Существует множество ограничений на классы значений, но одно из их огромных преимуществ заключается в том, что они позволяют методы расширения без штрафа за создание нового класса: если не требуется бокс, например, чтобы поместить класс значения в массив, это просто старый класс плюс набор методов, которые принимают класс в качестве первого параметра. Таким образом,
implicit class Foo(val i: Int) extends AnyVal {
def +*(j: Int) = i + j*j
}
разворачивается к чему-то, что может быть не дороже, чем писать i + j*j
самостоятельно (как только JVM вставит вызов метода).
К сожалению, одним из ограничений в SIP-15, который описывает классы значений, является
- Базовый тип 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
частный; в тех случаях, когда это было сделано, вы не сможете использовать этот трюк.