How to do ordering of a sealed trait?
Asked Answered
S

4

6

I have a distributed system defined by a sort of "state machine" ( "flow chart" )

each system writes there state in a shared "log"

I'm representing each state as part of a sealed trait and a given "status" for that state

I want to "merge/reduce" to a single status which represents the current progress.

(There are some relaxations in place as not all must succeed in order for the final status to complete successfully)

There are 2 sealed traits representing the flow:

sealed trait System
case object A extends System
case object B extends System
case object C extends System
...

sealed trait Status
case object Pending extends Status
case object InProgress extends Status
case object Success extends Status
case object Fail extends Status

Log:

A, Success
B, Fail
C, Pending
...
...

now there are a set of rules which I use to define a single status reduction

basically it gives a priority

A < B < C, ... < Z

and

Pending < InProgress < Success < Fail

so if there is a status of:

(A, Success) versus (C, Pending)

I want to reduce it to (C,Pending)

and if

(A,Success) versus (B, Fail)

I want to reduce it to (B, Fail)

I can model this as a simple integer comparison in my case (possibly with an outlier which I explicitly test for)

I'm not clear how to make the sealed traits comparible/orderable which would make my life way easier

something along these lines is adequate:

def reduce(states: Seq[(System,Status)]) : (System,Status) = {
    states.order... {left.system < right.system) && (a.status < b.status) ... possibly another ordering test ....}.tail // take the last one in the ordering
}
Sledge answered 3/9, 2019 at 14:48 Comment(0)
P
3

You can define a scala.math.Ordering[Status]:

object StatusOrdering extends Ordering[Status] {
  def compare(x: Status, y: Status): Int =
    (x, y) match {
      // assuming that the ordering is Pending < InProgress < Success < Fail...
      case (_, _) if (x eq y) => 0
      case (Pending, _) => -1
      case (_, Pending) => 1
      case (InProgress, _) => -1
      case (_, InProgress) => 1
      case (Success, _) => -1
      case (_, Success) => 1
      case _ => 0 // (Fail, Fail)
    }

In your reduce, you can then

import StatusOrdering.mkOrderingOps

and your Status objects will be enriched with < and friends.

It is also possible to have your trait extend Ordered[Status], which defines a canonical ordering in the trait:

sealed trait OrderedStatus extends Ordered[OrderedStatus] {
  def compare(that: OrderedStatus): Int =
    (this, that) match {
      case (x, y) if (x eq y) => 0
      case (Qux, _) => -1
      case (_, Qux) => 1
      case (Quux, _) => -1
      case (_, Quux) => 1
      case _ => 0
    }
}

case object Qux extends OrderedStatus
case object Quux extends OrderedStatus
case object Quuux extends OrderedStatus

Then you don't have to import mkOrderingOps, but I personally dislike the forward use of the extending case objects in the compare method (and the alternative of a boilerplate compare in each case object is even worse).

Pettway answered 3/9, 2019 at 15:24 Comment(1)
I don't understand how I use this now if I have a sequence of objectsSledge
D
2

One approach would be to define your priority of System and Status in a couple of Maps, followed by defining ordering of (System, Status) via Ordering.by:

val syMap: Map[System, Int] = Map(A->1, B->2, C->3)
val stMap: Map[Status, Int] = Map(Pending->1, InProgress->2, Success->3, Fail->4)

implicit val ssOrdering: Ordering[(System, Status)] =
  Ordering.by{ case (sy, st) => (syMap.getOrElse(sy, 0), stMap.getOrElse(st, 0)) }

import ssOrdering._

(A, Success) < (C, Pending)
// res1: Boolean = true

(A, Success) < (B, Fail)
// res2: Boolean = true

(C, Pending) < (B, Fail)
// res3: Boolean = false

Note that in the above sample code, the default values for non-matching System/Status are set to 0 (for lowest priority). They can be set to any other values, as needed.

To reduce a Seq of (System, Status)s:

def ssReduce(ss: Seq[(System, Status)])(implicit ssOrd: Ordering[(System, Status)]) : (System, Status) = {
  import ssOrd._
  ss.reduce((acc, t) => if (t < acc) acc else t )  // Or simply `ss.max`
}

ssReduce(Seq((A, Success), (C, Pending), (B, Fail)))
// res4: (System, Status) = (C,Pending)
Defalcate answered 3/9, 2019 at 16:30 Comment(0)
T
1

Consider enumeratum-cats CatsOrderValueEnum approach for defining order

import cats.Order
import enumeratum.values._
import cats.instances.int._

sealed abstract class Status(val value: Int) extends IntEnumEntry

object Status extends CatsOrderValueEnum[Int, Status] with IntEnum[Status] {
  case object Pending    extends Status(1)
  case object InProgress extends Status(2)
  case object Success  extends Status(3)
  case object Fail  extends Status(4)

  val values = findValues
}

object AdtOrder extends App {
  import Status._
  println(Order[Status].compare(Pending, Fail))
}

which outputs

-1

where

libraryDependencies ++= Seq(
  "com.beachape" %% "enumeratum" % "1.5.13",
  "com.beachape" %% "enumeratum-cats" % "1.5.15"
)
Teno answered 3/9, 2019 at 17:25 Comment(0)
R
0

Once you assign an integer to every case object you can write your own Ordering#compare method by just subtracting them:

sealed trait System
case object A extends System
case object B extends System
case object C extends System
case object D extends System
object System {
  implicit val ordering: Ordering[System] = new Ordering[System] {
    def compare(x: System, y: System): Int = {
      def id(value: System): Int = value match {
        case A => 0
        case B => 1
        case C => 2
        case D => 3
      }

      id(x) - id(y)
    }
  }
}

sealed trait Status
case object Pending extends Status
case object InProgress extends Status
case object Success extends Status
case object Fail extends Status

object Status {
  implicit val ordering: Ordering[Status] = new Ordering[Status] {
    def compare(x: Status, y: Status): Int = {
      def id(value: Status): Int = value match {
        case Pending => 0
        case InProgress => 1
        case Success => 2
        case Fail => 3
      }

      id(x) - id(y)
    }
  }
}

Then you can sort sequences easily:

val sorted = Seq((D, Fail), (D,Pending), (A,Fail), (D,InProgress)).sorted
// val sorted = List((A, Fail), (D, Pending), (D, InProgress), (D, Fail))
Reilly answered 31/5, 2023 at 12:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.