Наследование (иерархия классов) Equals and Ordered [T] (стирание параметра типа) - PullRequest
2 голосов
/ 06 сентября 2011

Я хочу иметь несколько классов данных, каждый из которых имеет идентификатор, назначенный достаточным для проверки равенства объектов.Но это id: I следует использовать не только для проверки равенства, но и для сравнения.Вот почему граница представления I <% Ordered[I] заявлена ​​в следующем базовом классе.

abstract class Ident[I <% Ordered[I]](val id: I)
  extends Equals with Ordered[Ident[I]]
{
  override lazy val hashCode = id.hashCode
  /* canEqual does not work as desired! */
  def canEqual(other: Any) = other match {
    case that: Ident[I] => true /* id < that.id || id > that.id || id == that.id */
    case _              => false
  }
  override def equals(other: Any) = other match {
    case that: Ident[I] => (that canEqual this) && this.id == that.id
    case _              => false
  }
  def _compare(that: Ident[I]): Int = {
    if (that canEqual this) this.id compare that.id else {
      val message = "'%s' and '%s' are not comparable!" format (this, that)
      throw new IllegalArgumentException(message)
    }
  }
  def compare(that: Ident[I]): Int = _compare(that)
}

Метод compare определяется только тогда, когда canEqual равен true.

Поскольку производные классыT <: Ident[I] тоже должно быть Ordered[T], неявное преобразование определено:

object Ident {
  implicit def ident2ordered[I, T <: Ident[I]](other: T): Ordered[T] = {
    new Ordered[T] {
      def compare(that: T): Int = other._compare(that)
    }
  }
}

А вот некоторые классы производных данных:

/* should be comparable with all other <: Ident[Int] */
class IntId(i: Int) extends Ident[Int](i)
/* should be comparable only with itself */
class YetAnotherIntId(y: Int) extends Ident[Int](y) {
  override def canEqual(other: Any) = other.isInstanceOf[YetAnotherIntId]
  override def equals(other: Any) = other match {
    case that: YetAnotherIntId => super.equals(that)
    case _                     => false
  }
}
/* should be comparable with all other <: Ident[Long] */
class LongId(j: Long) extends Ident[Long](j)
/* should be comparable with all other <: Ident[String] */
class StringId(s: String) extends Ident[String](s)

А теперь заметил (но не всегда)желаемое) поведение:

val i12 = new IntId(12)
val i13 = new IntId(13)
i12 canEqual i13 /* => true */
i12 < i13        /* => true */

val y12 = new YetAnotherIntId(12)
val y13 = new YetAnotherIntId(13)
y12 canEqual y13 /* => true */
y12 < y13        /* => true */

i12 canEqual y12 /* => true */
y12 canEqual i12 /* => false */
i12 == y12       /* => false */
y12 == i12       /* => false */

val j12 = new LongId(12L)
val j13 = new LongId(13L)
j12 canEqual j13 /* => true */
j12 < j13        /* => true */

i12 canEqual j12 /* => true  but want false because Int != Long */
j12 canEqual i12 /* => true  '' */
i12 == j12       /* => true  '' */
j12 == i12       /* => true  '' */

val s12 = new StringId("12")
val s13 = new StringId("13")
s12 canEqual s13 /* => true */
s12 < s13        /* => true */

i12 canEqual s12 /* => true  but want false because Int != String */
s12 canEqual i12 /* => true  '' */
i12 == s12       /* => false */
s12 == i12       /* => false */

Спасибо, если вы прочитали это далеко, но теперь вопросы:

Как мне добиться, чтобы Ident[I].canEqual(Ident[J]) было false для I != J без переопределения canEqual как в YetAnotherIntId?

Кажется, что Ident[I] это Ident[J] это Ident[_], что может привести к проблемам при использовании this.id и that.id вместекомментируя в Ident::canEqual (заменив true на this.id < that.id || this.id > that.id || this.id == that.id).

Итак, почему Ident[Int].canEqual(Ident[Long]) равно true?Из-за стирания типа?Можно ли "починить" это с Manifest?Или есть другая возможность убедиться, что I == J?

Ответы [ 2 ]

2 голосов
/ 06 сентября 2011

Тип стирания действительно. У тебя должно быть какое-то предупреждение на твоем case that: Ident[I]. Он проверяет, есть ли у вас Ident[somehing], но не то, что это I (так же, как в Java). Лучше проверить с that: Ident[_], если у вас нет ложной уверенности.

У вас действительно может быть Manifest в вашем классе, и проверьте, что манифесты равны.

class Ident[I <% Ordered](val id: I)(implicit val manifest: Manifest[I])
  def canEqual(that: Any) = that match {
    case Ident[_] if this.manifest == that.manifest => ...
    case _ => false
  }
}

Я бы предложил I : Ordering, а не I % Ordered (в общем, не только для вашей проблемы). Затем вы можете сравнить порядок, а не манифесты. Также вы должны использовать это, чтобы сделать то же самое для идентификаторов, так что вы знаете, что это соответствует порядку

Редактировать По поводу вашего комментария.

Я думаю, что этого и того же Ordering достаточно, чтобы сказать, что canEqual - это true. Затем у вас есть this.Odering, который известен компилятору Ordering[I], и that.Ordering, который является порядком для типа, который компилятор не знает. Вам придется использовать первое, что будет подразумевать неконтролируемое приведение (сопоставление с образцом или иное) этого .id к I. Однако проверки равенства порядка должно быть достаточно, чтобы обеспечить безопасное приведение (Ordering является инвариантом )

0 голосов
/ 07 сентября 2011

Относительно ответа Дидерда пытались использовать implicit ord: Ordering[I] - заменить в противном случае необходимо Manifest для обеспечения совместимости типов.Это прекрасно работает, и получилось следующее.

abstract class Ident[I](val id: I, dom: String = "")(implicit val ord: Ordering[I])
  extends Equals with Ordered[Ident[I]]
{
  protected val domain: AnyRef = if (dom != "") dom else ord

  override lazy val hashCode = id.##
  def canEqual(other: Any) = other match {
    case that: Ident[_] => this.domain eq that.domain
    case _              => false
  }
  override def equals(other: Any) = other match {
    /* ugly: non variable type-argument I in type pattern Ident[I]
       is unchecked since it is eliminated by erasure */
    case that: Ident[I] if this.domain eq that.domain
           => (that canEqual this) && ord.compare(this.id, that.id) == 0
    case _ => false
  }
  def compare(that: Ident[I]): Int = if (that canEqual this)
    ord compare (this.id, that.id)
  else
  {
    val message = "'%s' and '%s' are not comparable!" format (this, that)
    throw new IllegalArgumentException(message)
  }
  lazy val nameId: String = getClass.getSimpleName + "#" + id
  override def toString = nameId
}

Можно создать доменов (из иерархий подклассов), элементы которых сопоставимы между собой

/* Order, SubOrder and all other <: Ident[Int] without explicit dom are comparable */
class Order(i: Int) extends Ident[Int](i)
class SubOrder(i: Int) extends Order(i)

/* Node and SubNode's are in its own "Node" named domain
   and comparable only among each other */
class Node(i: Int) extends Ident[Int](i, "Node")
class SubNode(i: Int) extends Node(i)

без реализации отдельных canEqual шаблонов для каждого домена.

Осталось только одно: некрасивый метод идентификатора предупреждения equals.

...