Explore “herding cats”: Eq and Order

6 minute read

In that post we will check out the beginning of “herding cats” series by eed3si9n.

Eq

eed3si9n.com/herding-cats/Eq

Nothing special here, just can notice that anonymous given works well. With Scala 3 we can finally omit names like:

given Eq[IdCard] with {
  def eqv(a: IdCard, b: IdCard): Boolean = ???
}

No need to write given idCardEq: Eq[IdCard] with clear understanding that idCardEq won’t be used directly.

Finally, it’s always quite tempting to put Eq into companion object:

case class IdCard(firstName: String, secondName: String)

object IdCard:
  given Eq[IdCard] = Eq.fromUniversalEquals

Eq example

PartialOrder

trait PartialOrder[A] extends Eq[A]

Eugene didn’t pay much attention to that type class on the page PartialOrder.html

Pos.html page about partially ordered sets doesn’t bring much information too.

I recommend to take a look at least at wiki page: wiki/Partially_ordered_set

PartialOrder is all about notion of partial ordered sets.

  • Greatest element and least element: An element g in P is a greatest element if for every element a in P, a ≤ g. An element m in P is a least element if for every element a in P, a ≥ m. A poset can only have one greatest or least element.
  • Maximal elements and minimal elements: An element g in P is a maximal element if there is no element a in P such that a > g. Similarly, an element m in P is a minimal element if there is no element a in P such that a < m. If a poset has a greatest element, it must be the unique maximal element, but otherwise there can be more than one maximal element, and similarly for least elements and minimal elements.

That’s quite interesting how different ideas of greatest and maximal elements.

Naive implementations are:

def greatest[A](
  set: Set[A]
)(
  using partialOrdering: PartialOrder[A]
): Option[A] = 
  set.find(
    elem => set.forall(
      partialOrdering
        .tryCompare(_, elem)
        .exists(_ <= 0)
    )
  )

def maximals[A](
  set: Set[A]
)(
  using partialOrdering: PartialOrder[A]
): Set[A] =
  set.filter(
    elem => set.forall(
      partialOrdering
      .tryCompare(_, elem)
      .map(_ <= 0)
      .getOrElse(true)
    )
  )

def least[A](
  set: Set[A]
)(
  using partialOrdering: PartialOrder[A]
): Option[A] =
  set.find(
    elem => set.forall(
      partialOrdering
        .tryCompare(_, elem)
        .exists(_ >= 0)
    )
  )

def minimals[A](set: Set[A])(using partialOrdering: PartialOrder[A]): Set[A] =
  set.filter(
    elem => set.forall(
      partialOrdering
        .tryCompare(_, elem)
        .map(_ <= 0)
        .getOrElse(true)
    )
  )

I also combined values to case class. So we’ll have all properties at one place.

case class PosetDescription[A](
  greatest: Option[A], 
  least: Option[A], 
  minimals: Set[A], 
  maximals: Set[A]
)

PartialOrder example

It seemed that it is easy to abstract out sets itself to write something like:

def make[F[_]: Foldable, A](
  poset: F[A]
)(
  using partialOrdering: PartialOrder[A]
) = PosetDescription(
  greatest = poset.find(el => poset.forall(_.tryCompare(el).exists(_ <= 0)))
...

In fact, we’re interested in the properties of set itself instead of just provided API.

There is also interesting idea of bounds. For a subset A of P, an element x in P is an upper bound of A if ax, for each element a in A. In particular, x need not be in A to be an upper bound of A. Similarly, an element x in P is a lower bound of A if ax, for each element a in A. A greatest element of P is an upper bound of P itself, and a least element is a lower bound of P. Nevertheless, writing code to decouple strongly connected components is out of scope. Let’s move forward.

Order

trait Order[A] extends PartialOrder[A]

Unlike PartialOrder we can meet Order[A] everywhere. We can’t write just:

val persons = NonEmptySet.of(john, anton)
no implicit argument of type cats.kernel.Order[io.github.antonkw.IdCard] was found for parameter A of method of in object NonEmptySetImpl

Signature is following: def of[A](a: A, as: A*)(implicit A: Order[A]): NonEmptySet[A]

Hence, we need to implement order:

given Order[IdCard] = Order.from((id1, id2) => {
  val initial = id1.firstName compare id2.firstName
  if (initial == 0) id1.secondName compare id2.secondName
  else initial
})

Order example