I am trying to make a sequence (for example, other collection types are also conceivable) comparable to other sequences.
class RichSeq[A](val seq: Seq[A]) extends Ordered[RichSeq[A]]
Of course there is a implicit conversion in the refered package object:
implicit def seq2RichSeq[A](s: Seq[A]) = new RichSeq(s)
Comparing means, first size matters than each element. Code makes it clear:
class RichSeq[A](val seq: Seq[A]) extends Ordered[RichSeq[A]] {
def compare(s: RichSeq[A]) = {
seq.size compare s.seq.size match {
case 0 => seq.view.zip(s.seq).map { case (x,y) => ord.compare(x,y) }.dropWhile(_ == 0).headOption.getOrElse(0)
case x => x
}
}
}
But that doesn`t compile (of course) because one needs an ordering to compare the elements, so I tried that:
class RichSeq[A](val seq: Seq[A]) extends Ordered[RichSeq[A]] {
def compare(s: RichSeq[A])(implicit ord: Ordering[A]) = {
// ...
}
}
Now the signature of the compare method is not suitable, so I moved the implicit ord
to the class signature (and adapted the implicit conversion):
implicit def seq2RichSeq[A](s: Seq[A])(implicit ord: Ordering[A]) = new RichSeq(s)
class RichSeq[A](val seq: Seq[A])(implicit ord: Ordering[A]) extends Ordered[RichSeq[A]] {
def compare(s: RichSeq[A]) = {
// ...
}
}
But now I have a the problem, that all other methods in RichSeq
that I want to use via implicit
at a Seq[A]
also require an implicit Ordering[A]
and I can´t always deliver one. Sometimes I use my RichSeq
by methods without Ordering and sometimes the compare method.
For example, sometimes I call
def distinctBy[B](f: A => B): Seq[A] = {
seq.foldLeft { (Buffer[A](),MutMap[B,A]()) } {
case ((b,m),x) if m contains f(x) => (b,m)
case ((b,m),x) =>
m += f(x) -> x
b += x
(b,m)
}._1
}
meanwhile I am not able to define an Ordering[A]
.
I see one solution in having two different classes (with two implicit conversions):
class RichSeqOrderable[A](val seq: Seq[A])(implicit ord: Ordering[A]开发者_C百科) extends Ordered[RichSeqOrderable[A]]
class RichSeq[A](val seq: Seq[A])
But I think that breaks the thought of having all stuff together?!?
My usual preface that I wouldn't necessarily do things this way, but to use the question as an excuse to illuminate some lesser known features: here if any implicit ordering is available it will use that, but otherwise it will order them by hashcode.
package object foo {
implicit def seq2RichSeq[A](s: Seq[A])(implicit ord: Ordering[A] = Ordering[Int].on((_: A).##)) = new RichSeq(s)
}
package foo {
class RichSeq[A](val seq: Seq[A])(implicit ord: Ordering[A]) extends Ordered[RichSeq[A]] {
def compare(s: RichSeq[A]) = {
seq.size compare s.seq.size match {
case 0 => seq.view.zip(s.seq).map { case (x,y) => ord.compare(x,y) }.dropWhile(_ == 0).headOption.getOrElse(0)
case x => x
}
}
}
}
I went for something similar to paulp's suggestion:
class RichSeq[A](val seq: Seq[A])(implicit optionalOrd: Option[Ordering[A]] = None) extends Ordered[RichSeq[A]] {
def compare(s: RichSeq[A]) = {
seq.size compare s.seq.size match {
case 0 => seq.view.zip(s.seq).map { case (x,y) => optionalOrd.map(_.compare(x,y)).getOrElse(0) }.dropWhile(_ == 0).headOption.getOrElse(0)
case x => x
}
}
}
object RichSeq {
implicit def orderingToSome[A](implicit ord: Ordering[A] = null) = Option(ord)
implicit def seq2RichSeq[A](s: Seq[A])(implicit ord: Option[Ordering[A]]) = new RichSeq(s)
}
It's not a good thing to have too many implicits, particularly of types in the standard library. However, I think Ordering[A] => Option[Ordering[A]]
is about as safe as it could be.
On chaining of implicits
Scala has a restriction on automatic conversions to add a method, which is that it won't apply more than one conversion in trying to find methods. For example:
class A(val n: Int)
class B(val m: Int, val n: Int)
class C(val m: Int, val n: Int, val o: Int) {
def total = m + n + o
}
// This demonstrates implicit conversion chaining restrictions
object T1 { // to make it easy to test on REPL
implicit def toA(n: Int) = new A(n)
implicit def aToB(a: A) = new B(a.n, a.n)
implicit def bToC(b: B) = new C(b.m, b.n, b.m + b.n)
// won't work
println(5.total)
println(new A(5).total)
// works
println(new B(5, 5).total)
println(new C(5, 5, 10).total)
}
However, if an implicit definition requires an implicit parameter itself, Scala will look for additional implicit values for as long as needed. Continue from the last example:
// def m[A <% B](m: A) is the same thing as
// def m[A](m: A)(implicit ev: A => B)
object T2 {
implicit def toA(n: Int) = new A(n)
implicit def aToB[A1 <% A](a: A1) = new B(a.n, a.n)
implicit def bToC[B1 <% B](b: B1) = new C(b.m, b.n, b.m + b.n)
// works
println(5.total)
println(new A(5).total)
println(new B(5, 5).total)
println(new C(5, 5, 10).total)
}
"Magic!", you might say. Not so. Here is how the compiler would translate each one:
object T1Translated {
implicit def toA(n: Int) = new A(n)
implicit def aToB(a: A) = new B(a.n, a.n)
implicit def bToC(b: B) = new C(b.m, b.n, b.m + b.n)
// Scala won't do this
println(bToC(aToB(toA(5))).total)
println(bToC(aToB(new A(5))).total)
// Just this
println(bToC(new B(5, 5)).total)
// No implicits required
println(new C(5, 5, 10).total)
}
object T2Translated {
implicit def toA(n: Int) = new A(n)
implicit def aToB[A1 <% A](a: A1) = new B(a.n, a.n)
implicit def bToC[B1 <% B](b: B1) = new C(b.m, b.n, b.m + b.n)
// Scala does this
println(bToC(5)(x => aToB(x)(y => toA(y))).total)
println(bToC(new A(5))(x => aTo(B(x)(identity _)).total)
println(bToC(new B(5, 5))(identity _).total)
// no implicits required
println(new C(5, 5, 10).total)
}
So, while bToC
is being used as an implicit conversion, aToB
and toA
are being passed as implicit parameters, instead of being chained as implicit conversions.
I have not coded it up completely, but why don't you do:
class RichSeq[A <: Ordered[A]](val seq: Seq[A]) extends Ordered[RichSeq[A]] {
import Ordering.ordered
...
}
The imported implicit conversion will give you Ordering[A]
when necessary.
精彩评论