Sunday, 20 October 2013

Improving Scala sorting DSL

Today I encountered an annoying lacking in Scala list sorting support. I just wanted to sort my list by using different fields, some causing an ascending order others causing a descending order.

Well, everything went happily (though with ugly syntax) with Scala´s List.sortBy and unary minus operators... until I needed to perform mixed ascending/descending sorts with strings. Not so easy to implement elegantly.

I tried to google answers but didn't find any useful results. That´s why I decided to make my own syntax by using generics, Ordered trait inheritance and Scala´s implicit Ordering support. I ended up making the following implementation:

object OrderingDSL {
case class AscendingOrder[T](value: T)(implicit ord: Ordering[T]) extends Ordered[AscendingOrder[T]] {
def compare(that: AscendingOrder[T]): Int = ord.compare(value, that.value)
}
case class DescendingOrder[T](value: T)(implicit ord: Ordering[T]) extends Ordered[DescendingOrder[T]] {
def compare(that: DescendingOrder[T]): Int = -ord.compare(value, that.value)
}
def asc[T](value: T)(implicit ord: Ordering[T]) = AscendingOrder(value)
def desc[T](value: T)(implicit ord: Ordering[T]) = DescendingOrder(value)
}
Now I can sort my collections (at least in Scala 2.10) with neat syntax:
myList.sortBy(v => (asc(v.mem1), desc(v.mem2), ...))

Syntax should work with all classes having Scala Ordering trait implemented (at least all built-in basic types in Scala). Here is a working example how to use the DSL:

object OrderingDSLExample extends App {
case class My(num: Int, str: String)
val myValues = List(My(3, "foo"), My(3, "bar"), My(5, "bar"), My(5, "foo"))
import OrderingDSL._
require(myValues.sortBy(m => (asc(m.num), asc(m.str))) ==
List(My(3, "bar"), My(3, "foo"), My(5, "bar"), My(5, "foo")))
require(myValues.sortBy(m => (asc(m.num), desc(m.str))) ==
List(My(3, "foo"), My(3, "bar"), My(5, "foo"), My(5, "bar")))
require(myValues.sortBy(m => (desc(m.num), asc(m.str))) ==
List(My(5, "bar"), My(5, "foo"), My(3, "bar"), My(3, "foo")))
require(myValues.sortBy(m => (desc(m.num), desc(m.str))) ==
List(My(5, "foo"), My(5, "bar"), My(3, "foo"), My(3, "bar")))
println("Horray!")
}
I hope this post helps. Happy sorting!

No comments:

Post a Comment