Я пошел за чем-то похожим на предложение paulp :
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)
}
Нехорошо иметь слишком много следствий, особенно типов в стандартной библиотеке. Тем не менее, я думаю, что Ordering[A] => Option[Ordering[A]]
настолько безопасен, насколько это возможно.
О цепочке имплицитов
В Scala есть ограничение на автоматическое преобразование для добавления метода, заключающееся в том, что при поиске методов он не будет применять более одного преобразования. Например:
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)
}
Однако, если для неявного определения требуется сам неявный параметр, Scala будет искать дополнительные неявные значения столько времени, сколько потребуется. Продолжите с последнего примера:
// 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)
}
«Волшебство!», Скажете вы. Не так. Вот как компилятор будет переводить каждый из них:
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)
}
Таким образом, в то время как bToC
используется как неявное преобразование, aToB
и toA
передаются как неявные параметры , а не как цепочка неявных преобразований.