Переопределить метод equals() пары
Этот вопрос ранее был задан в scala -случающем списке рассылки без подтвержденного ответа.
scala> val T = new Pair(1, 2){
override def equals(obj:Any) = obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1}
}
T: (Int, Int) = (1,2)
scala> T match {
case (1, 1) => println("matched")
case _ => println("not matched")
}
not matched
scala> (1, 1) match {
case T => println("matched")
case _ => println("not matched")
}
not matched
scala> T == (1, 1)
res15: Boolean = true
Я думал, что результат согласования с константой (val) зависит от возвращаемого значения "equals", но результаты показывают, что это не так, тогда каковы критерии?
Кто-то предположил, что case (1, 1) =>
является шаблоном экстрактора и вместо этого использует Tuple2.unapply
. поэтому я пробовал:
scala> Pair.unapply(T)
res1: Option[(Int, Int)] = Some((1,2))
scala> Pair.unapply(T).get == (1, 1)
res2: Boolean = true
Может кто-нибудь объяснить, почему ==
получить правду, но я не могу заставить их соответствовать?
Ответы
Ответ 1
С разрешением # 3888 я могу дать окончательный ответ на этот вопрос.
-
T match { case (1, 1) =>
Нет, это не имеет никакого отношения к unapply
. Как упоминалось в extempore, case (1,1) =>
является "шаблоном Tuple", псевдоним для "шаблона конструктора" класса case Tuple2, он соответствует только значениям, построенным как Tuple2 (1, 1) или Pair (1, 1).
Что действительно касается unapply
- это "Шаблон экстрактора":
object Pair {
val T = new Pair(1,1){
def unapply(p:(Int, Int)) :Boolean = this._1 == p._1
}
def main(args: Array[String]) = {
(1, 2) match {
case T() => println("matched")
case _ => println("not matched")
}
}
}
Вы получаете "соответствие". Обратите внимание на ()
в разделе case
-
(1, 1) match { case T => ...
в соответствии с scala spec, раздел 8.1.5, это "шаблон стабильного идентификатора", case T
соответствует любому значению v, так что T == v. Поэтому мы ДОЛЖНЫ получить "соответствие". Результаты "несоответствия"
просто вызваны ошибкой в текущей реализации компилятора.
Ответ 2
Проблема с вашим примером заключается в том, что вы переопределяете только метод equals
анонимного класса, которым вы определяете свой собственный кортеж. Познакомьтесь с тем, что вы делаете при запуске кода, который вы здесь указали.
val p = new Pair(1, 2) {
override def equals(obj:Any) = {
obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
}
}
Что здесь делает Scala, он создает новый анонимный класс, который расширяет Pair
и переопределяет его равные. Таким образом, это эквивалентно запуску следующего кода:
class Foo extends Pair(1,2) {
override def equals(obj:Any) = {
obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
}
}
val p = new Foo
И вот где вы можете точно увидеть, в чем проблема! Определение equals
не является симметричным. p == (1,1)
оценивается как true
, а (1,1) == p
- false
! Это связано с тем, что первая эквивалентна p.equals((1,1))
, а последняя эквивалентна (1,1).equals(p)
. В примере, который вы указали, он не работает, потому что объект в этом случае сравнивается с сопоставленным объектом, а не наоборот. Поэтому, как вы указали, Pair.unapply(p).get == (1, 1)
оценивается как true
, однако (1,1) == Pair.unapply(p).get
оценивается как false
, и кажется, что последним является тот, который используется при сопоставлении.
Тем не менее, в любом случае создание равенства, которое не является симметричным, действительно очень плохой идеей, поскольку выполнение кода зависит от порядка, в котором вы сравниваете объекты. Кроме того, у равных вы определили дополнительную проблему - это с ошибкой при попытке сравнить ваш p
с любым Pair
, который не относится к типу (Int, Int)
. Это связано с тем, что после стирания типа (как JVM реализует дженерики), Pair
больше не параметризуется типами его составляющих. Следовательно, (Int, Int)
имеет тот же тип, что и (String, String)
, и, следовательно, следующий код с ошибкой завершится ошибкой:
p == ("foo", "bar")
поскольку Scala попытается применить a (String, String)
к (Int, Int)
.
Если вы хотите реализовать эту функциональность, самое легкое, что вы могли бы сделать, - использовать шаблон моей библиотеки, сутенерство Pair
. Однако вы не должны называть свой метод equals
. Назовите это чем-то другим, например ~=
. Я должен идти сейчас, однако, когда я вернусь, я могу дать вам код для этого. Это довольно легко. Вы должны посмотреть на реализацию equals
в паре и удалить часть, которая сравнивает второй аргумент:)
Ответ 3
Я должен сказать, что Scala удивил меня снова. Еще несколько тестов показывают, что результат зависит от контекста работы.
Если вы запустите:
object Pair{
def main(args:Array[String]) {
val T = new Pair(1, 2){
override def equals(obj:Any)= obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
}
(1, 1) match {
case T => println("matched")
case _ => println("not matched")
}
}
}
вы получаете: соответствие
И если вы запустите:
object Pair extends Application {
val T = new Pair(1, 2){
override def equals(obj:Any)= obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
}
(1, 1) match {
case T => println("matched")
case _ => println("not matched")
}
}
вы получаете: не соответствует
Я всегда думал, что нет никакой разницы между кодом в методах main() и кодом в теле объекта, который расширяет черту Application. Действительно странно.
Ответ 4
Серпухи имеют привилегию в шаблоне. Они не ваши повседневные занятия. Это в спецификации, раздел 8.1.7, "Tuple Patterns".
Когда вы говорите
(1, 1) match { case T ...
Тогда на (1, 1) вызывается равенство, которое, конечно же, не говорит, не равно.
Когда вы говорите
T match { case (1, 1) => ...
Затем ваш метод equals игнорируется из-за шаблона кортежа, а T._1 сравнивается с 1 и T._2 с 1, и снова он не соответствует.