Как вы обогащаете классы стоимости без накладных расходов?
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
, но выполняет сложное битовое смешение), вы можете просто rewrap базовый класс в классе значений, который вы пытаетесь расширить, где бы вы ни нуждались.
(Обратите внимание, что вы получите предупреждение, если вы не import language.implicitConversions
.)
Приложение: в Scala 2.11+ вы можете сделать val
приватным; для случаев, когда это было сделано, вы не сможете использовать этот трюк.