I went through the documentation and found a couple of ways how to define ad-hoc behavior for type class instances. Here's the original example:
trait Ord[T] {
def compare(x: T, y: T): Int
extension (x: T) def < (y: T) = compare(x, y) < 0
extension (x: T) def > (y: T) = compare(x, y) > 0
}
The funny thing is we could define a bunch of ordinary infix methods alongside with extensions. The user expected behavior should be the same (despite the implementation differences).
trait RankEq[T]:
extension (x: T)
def equal (y: T): Boolean
extension (x: T)
def notEqual (y: T): Boolean = !rankEqual(y)
trait RankOrd[T] {
def rankCompare(x: T, y: T): Int
def (x: T) isLower (y: T): Boolean =
rankCompare(x, y) == -1
def (x: T) isHigher (y: T): Boolean =
rankCompare(x, y) == 1
}
Here's the example which illustrates that:
given rankEq as RankEq[Rank] {
extension (x: Rank)
def equal (y: Rank) = (x.ordinal - y.ordinal) == 0
}
given rankOrd as RankOrd[Rank] {
override def rankCompare(x: Rank, y: Rank): Int = {
x.ordinal - y.ordinal
}
}
object Rank {
def beats(r1: Rank, r2: Rank)
(using RankEq[Rank])(using RankOrd[Rank]): Boolean = {
if r1.equal(r2) then false // extension methods
else r1.isHigher(r2) // trait-member infix method
}
}
Since both constructs are doing the same thing (in different ways), it rises a question: which way is the most idiomatic? If both, in which situations each works best?
Thanks!