Стиль кодирования для операторов перегрузки - PullRequest
1 голос
/ 21 июня 2019

Предположим, что у меня есть класс с именем Rational, который представляет рациональные числа "чисто", то есть он поддерживает представление a / b как (a, b) и реализует обычные операторы +, -, *, / и другие для работы с этими кортежами, вместо оценки фактических дробей для каждой операции.

Предположим теперь, что я хочу определить, что произойдет, если я добавлю Rational экземпляр к Int, в дополнение к уже определенному поведению для Rational, добавленному к Rational. Тогда, конечно, я мог бы в конечном итоге захотеть добавить Rational к Double или к Float, BigInt другим числовым типам ...

Подход № 1: Предоставить несколько реализаций +(Rational, _):

def + (that:Rational):Rational  = {
    require(that != null, "Rational + Rational: Provided null argument.")
    new Rational(this.numer * that.denom + that.numer * this.denom, this.denom * that.denom)
}

def + (that:Int): Rational = this + new Rational(that, 1) // Constructor takes (numer, denom) pair

def + (that:BigInt): Rational = ....
.
.
.

Подход № 2: Сопоставление с образцом на Any:

def + (that:Any):Rational  = {
    require(that != null, "+(Rational, Any): Provided null argument.")
    that match {
        case that:Rational => new Rational(this.numer * that.denom + that.numer * this.denom, this.denom * that.denom)
        case that:Int | BigInt => new Rational(this.numer + that * this.denom, this.denom) // a /b + c = (a + cb)/b
        case that:Double => ....
        .
        .
        .
        case _ => throw new UnsupportedOperationException("+(Rational, Any): Unsupported operand.")
     }
}

Одно из преимуществ подхода сопоставления с образцом, которое я вижу, - это сохранение с точки зрения фактических строк исходного кода, но, возможно, с уменьшением читабельности. Возможно, что еще более важно, я могу контролировать то, что я делаю, когда мне предоставляют тип, для которого я не определила поведение +. Я не уверен, как этого можно достичь с помощью первого подхода, возможно, путем добавления перегрузки для Any под всеми остальными? В любом случае, это звучит опасно.

Идеи о том, следует ли выбирать первый или второй подход? Есть ли проблемы с безопасностью, которых я не вижу? Я открываю себя для ClassCastException s или других видов исключений?

1 Ответ

3 голосов
/ 21 июня 2019

Способ применения ошибки времени компиляции состоит в том, чтобы гарантировать, что метод plus на самом деле не может принимать тип Any через ограничение типа, неявный параметр или тому подобное.

В одну сторонуиметь дело с этим было бы использовать класс типа scala Numeric.Должно быть вполне возможно создать экземпляр для Rational, так как вы можете легко реализовать все необходимые методы, и в этот момент вы можете определить plus как

def +[T: Numeric](that: T) : Rational

Теперь вы также будетевозможность извлекать методы toInt / toLong / toFloat / toDouble неявного аргумента Numeric для обработки неизвестных классов вместо того, чтобы также выдавать ошибку времени выполнения, если вы хотели - и даже если вы нет. вы, по крайней мере, значительно сократили ошибочные типы, которые можно передавать.

Вы также можете определить свой собственный класс типов и соответствующие его экземпляры для типов, которые вы хотите поддерживать.Затем вы можете либо оставить логику сложения в методе +, либо переместить ее в экземпляры класса типов:

trait CanBeAdded[T] {
  def add(t: T, rational: Rational) : Rational
}

object CanBeAdded {
  implicit val int = new CanBeAdded[Int] {
    override def add(t: Int, rational: Rational): Rational = ???
  }

  implicit val long = new CanBeAdded[Long] {
    override def add(t: Long, rational: Rational): Rational = ???
  }

  implicit val rational = new CanBeAdded[Rational] {
    override def add(t: Rational, rational: Rational): Unit = ???
  }
}

case class Rational(a: BigInt, b: BigInt) {
  def +[T: CanBeAdded](that: T) = implicitly[CanBeAdded[T]].add(that, this)
}

Мне нравится второй вариант, потому что я сомневаюсь, что ваш тип Rational будетдобавление к любому числовому типу имеет смысл.Вы упоминаете, что хотите, чтобы + мог принимать Double с, но точное представление в сочетании с ошибками округления, которые часто возникают в Double с, кажется, что это может привести к очень странному и нелогичному поведению с результатамиэто не имеет особого смысла.

...