Изменение параметра конструктора класса case перед установкой значения

Есть ли способ в Scala, чтобы изменить параметр, переданный в метод конструктора класса аргументов/аргумент/apply(), прежде чем он станет val? Например.

case class AbsVal private(aVal: Double)

object AbsVal {
    def apply(aVal: Double): AbsVal = AbsVal(Math.abs(aVal)) // doesn't compile
}

Это, конечно, не соответствует неоднозначной ссылке на перегруженное определение. Я подумал, что, возможно, я мог бы обмануть его с помощью именованных параметров (и разными именами параметров для конструктора vs apply()), но это тоже не сработает.

Конечно, вместо apply() я мог бы просто иметь частный конструктор и метод factory, но это раздражало, что нужно подставлять код AbsVal.make(x) вместо AbsVal(x).

Ответ 1

abstract case class AbsVal private(value: Double)

object AbsVal {
  def apply(value: Double): AbsVal = new AbsVal(Math.abs(value)) {}
}

Абстрактные классы case не имеют метода применения, автоматически генерируемого в их сопутствующем объекте. Это позволяет нам создавать свои собственные. Мы должны создать другой класс для наследования класса case (анонимный здесь), но метод toString, сгенерированный для класса case, по-прежнему будет отображать его как AbsVal, поэтому это также должно быть невидимым. Метод copy не генерируется автоматически для абстрактных классов case, но для класса с единственным параметром, подобным этому, он все равно не будет полезен (и его всегда можно определить вручную, см. Ответ LimbSoup).

Наследование класса класса обычно плохая идея, но из-за частного конструктора единственным подклассом, который будет существовать, будет анонимный, который мы определяем, поэтому в этом случае это безопасно.

Ответ 2

Кажется, что нет способа переопределить apply в классе case. В вашем коде есть синтаксические ошибки, но даже изменение его на override def apply(value: Double): AbsVal = new AbsVal(Math.abs(value)) завершится ошибкой. Я думаю, что это поведение преднамеренно, потому что, когда вы определяете AbsVal(-1), вы ожидаете, что значение не изменится (для любого класса case).

Тем не менее, такое же чувство может быть достигнуто с помощью класса с конструктором private. Очевидно, немного больше работы, чтобы получить ту же функциональность, что и класс case, но ваш AbsVal.make исчез.

class AbsVal private(val value: Double) {

    def copy(value: Double = this.value): AbsVal = AbsVal(value)

    def equals(that: AbsVal): Boolean = this.value.equals(that.value)

    override def toString: String = "AbsVal(" + this.value.toString + ")"

}

object AbsVal {

    def apply(value: Double):AbsVal = new AbsVal(Math.abs(value))

    def unapply(a: AbsVal): Option[Double] = Some(a.value)

}

Ответ 3

Это раздражает, чтобы поднять код AbsVal.make(x) вместо просто AbsVal(x)

Это чрезвычайно субъективный момент. На разных языках общепринято предпочитать описательные имена для перегруженных определений, поскольку они, как правило, вводят двусмысленность. Ваш прекрасный пример такого случая.

Следовательно, AbsVal.make(x) - правильный способ пойти в вашей ситуации. Хотя я бы назвал его чем-то вроде AbsVal.abs(x).