Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit ordering of case classes scala

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.

like image 352
aaronman Avatar asked Jul 23 '14 17:07

aaronman


2 Answers

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.

like image 57
Miles Sabin Avatar answered Sep 30 '22 14:09

Miles Sabin


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.

like image 35
Travis Brown Avatar answered Sep 30 '22 14:09

Travis Brown