Given a sum ADT:
sealed trait A
case object B extends A
case object C extends A
sealed trait D extends A
case object E extends Ewhere we might desire a cats.kernel.Order - specifically, we might desire the sorted order of the
members of our sum adt to be like E, C, B. If we want to ensure implementation is typesafe, the only real option we are
left with is to define a pairwise comparison function. Perhaps we have something like this:
implicit def forOrdering: Ordering.fromLessThan { (a, b) match {
case (E, _) => true
case (_, E) => false
case (D, _) => true
case (_, D) => false
case (B, B) => false
}
}The problem here is that this is quite verbose (even for a sum ADT with only 3 members!)
Using order-shapeless, we can specify a Coproduct with our desired ordering like
import io.masonedmison.ordershapeless.orderByCoproduct
import shapeless.{CNil, :+:}
// define Ordering as coproduct - must contain all elements within our sum adt, else we will get a compile error
type OrderForA = E :+: C :+: B :+: CNil
val forOrder: Order[A] = orderByCoproduct[OrderForA, A]
implicit val forOrdering = forOrder.toOrdering
val sortedAs = List(B, C, E).sorted // List(E, C, B)Also note that this is typesafe, ie if we pass a Coproduct that does not contain the same members of our sum ADT, we will get a compile error:
type OrderForA = C :+: B :+: CNil // missing E...
val forOrder: Order[A] = orderByCoproduct[OrderForA, A] // fails to compile with "could not find implicit value for parameter align..."In some cases, you might have a case class that has a set of fields of which you don't want to be considered as part of sorting. For example, given a case class like:
case class Coffee(id: UUID, name: String, origin: String, price: Double, hasBeenGround: Boolean)We might want to exclude the field id as part of our Order.
We can easily specify this as follows:
import io.masonedmison.ordershapeless.{CaseClassField, exclude}
import shapeless.{::, HNil}
// We specify the fields to exlude as an HList of `CaseClassField`s
// A `CaseClassField[Name, T]` is an alias for a `shapeless.FieldType` where `Name` is a singleton type of the "name" of the field,
// and T is the type of the **field**.
type Exclude = CaseClassField["id", Int] :: HNil
val forOrder: Order[Coffee] = exclude[Coffee, Exclude]
val c1 = Coffee(1, "Kochere", "Ethiopa", 15.75, false)
val c2 = Coffee(2, "Dumo", "Costa Rica", 20.99, false)
forOrder.lt(c1, c2) // false since "Kochere" > "Dumo" and since id is not considered as part of ordering.Considering our Coffee case class from above, say that we would like to Order a Coffee using the fields price, origin, name in that specific order.
We can do this like:
// Note that our reordering musn't conatain _all of the_ fields of our case class, a subset will do.
type Reorder = CaseClassField["price", Double] :: CaseClassField["origin", String] :: CaseClassField["name": String] :: HNil
val forOrder: Order[Coffee] = reorderProduct[Coffee, Reorder]
val c1 = Coffee(1, "Kochere", "Ethiopa", 15.75, false)
val c2 = Coffee(2, "Dumo", "Costa Rica", 20.99, false)
forOrder.lt(c1, c2) // true since 15.75 < 20.99