Consider the following:
scala> case class Foo(i: Int, j: Int)
defined class Foo
scala> def foo[T : Equiv](a: T, b: T): Unit = {
| if(implicitly[Equiv[T]].equiv(a,b))
| println("EQ!")
| else
| println("neq...")
| }
foo: [T](a: T, b: T)(implicit evidence$1: Equiv[T])Unit
scala> foo(Foo(1,2),Foo(1,2))
EQ!
scala> foo(Foo(1,2),Foo(3,2))
neq...
as expected, now let’s define a custom Equiv
:
scala> :paste
// Entering paste mode (ctrl-D to finish)
case class Bar(i: Int, j: Int)
object Bar {
implicit val equiv = new Equiv[Bar] {
override def equiv(a: Bar, b: Bar): Boolean = a.i == b.i
}
}
// Exiting paste mode, now interpreting.
defined class Bar
defined object Bar
scala> foo(Bar(1,2),Bar(1,3))
EQ!
again, this is expected, but:
scala> foo(Option(Bar(1,2)),Option(Bar(1,3)))
neq...
scala> foo(Option(Bar(1,2)),Option(Bar(1,2)))
EQ!
as can be seen, Equiv[Bar]
isn’t being invoked.
The reason is, that Option
's implicit being resolved is:
trait OptionOrdering[T] extends Ordering[Option[T]] { … }
implicit def Option[T](implicit ord: Ordering[T]): Ordering[Option[T]] =
new OptionOrdering[T] { val optionOrdering = ord }
Since Ordering
extends PartialOrdering
which extends Equiv
.
both PartialOrdering
& Ordering
implementations override equiv
:
trait PartialOrdering[T] extends Equiv[T] {
…
def equiv(x: T, y: T): Boolean = lteq(x,y) && lteq(y,x)
…
}
trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializable {
…
override def equiv(x: T, y: T): Boolean = compare(x, y) == 0
…
}
Because of that, I think OptionOrdering
's implementation should be changed.
this is the current implementation:
trait OptionOrdering[T] extends Ordering[Option[T]] {
def optionOrdering: Ordering[T]
def compare(x: Option[T], y: Option[T]) = (x, y) match {
case (None, None) => 0
case (None, _) => -1
case (_, None) => 1
case (Some(x), Some(y)) => optionOrdering.compare(x, y)
}
}
and I think that equiv
should be added to OptionOrdering
explicitly:
override def equiv(x: Option[T], y: Option[T]): Boolean = {
if(x.isEmpty) y.isEmpty
else y.fold(false){ t =>
implicitly[Equiv[T]].equiv(x.get,t)
}
}
This should be fine, since Equiv
companion extends LowPriorityEquiv
which will handle any T
thanks to universal
.
Also, like Option
, other Ordering
implicits should get the same treatment (seqDerivedOrdering
,Iterable
,Tuple2
,…,Tuple9
).