Case classes do not seem to have an implicit ordering in scala.
scala> case class A(i:Int)
defined class A
scala> implicitly[Ordering[A]]
<console>:10: error: No implicit Ordering defined for A.
implicitly[Ordering[A]]
I want to know if there is anyway to generically define an implicit ordering for all case classes, if not is there at least a way to define implicit ordering for each arity of case classes / case classes of the same member types.
Orderings for case classes can be automatically derived using shapeless,
import GenericOrdering._
case class Foo(i : Int, s : String)
implicitly[Ordering[Foo]]
val fs = List(
Foo(2, "b"),
Foo(2, "a"),
Foo(1, "c")
).sorted
assert(fs == List(
Foo(1, "c"),
Foo(2, "a"),
Foo(2, "b")
))
See here for the complete example. The full mechanics and an extension to PartialOrdering
will be part of the forthcoming shapeless 2.1.0 release.
For the sake of completeness I'll be the bad angel to Miles's good solution. You can in fact roll your own version of this functionality pretty easily with a macro:
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
object OrderingHelper {
implicit def apply[A]: Ordering[A] = macro apply_impl[A]
def apply_impl[A: c.WeakTypeTag](c: Context) = {
import c.universe._
val A = weakTypeOf[A]
val fields = A.decls.collect {
case sym: MethodSymbol if sym.isCaseAccessor => q"a.${sym.name}"
}
if (fields.isEmpty) c.abort(c.enclosingPosition, "Not a case class!") else
A.baseClasses.collectFirst {
case sym
if sym.name.decodedName.toString.startsWith("Tuple")
&& sym.owner == typeOf[Any].typeSymbol.owner =>
c.abort(c.enclosingPosition, "Not needed for tuples!")
} getOrElse c.Expr[Ordering[A]](q"Ordering.by((a: $A) => (..$fields))")
}
}
And then:
scala> import OrderingHelper._
import OrderingHelper._
scala> case class B(i: Int, s: String)
defined class B
scala> Ordering[B]
res0: scala.math.Ordering[B] = scala.math.Ordering$$anon$9@2c9df057
scala> Ordering[B].compare(B(1, "foo"), B(1, "bar"))
res1: Int = 1
The code above will work in the REPL with no extra dependencies in 2.11, and for 2.10 you just need some small adjustments and a compiler plugin (see my blog post for details).
I'd definitely recommend the Shapeless approach, though—Shapeless gives you a much more usefully constrained toolkit for this kind of generic programming.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With