Derek Peirce
11/18/2025, 10:14 AMComparable by delegation. For instance,
data class Player(val name: String, val score: Int): Comparable<Player> by (score, name)
would be short for adding:
override fun compareTo(other: Player) {
return if (score != other.score) {
score.compareTo(other.score)
} else {
name.compareTo(other.name)
}
}
I don't know if this system would make much sense for any other interface, but Comparable<Player> by (score, name) reads very nicely to convey, "players are compared first by score, then by name." It would be similar to data eliminating similar boilerplate code for equals and hashCode.Youssef Shoaib [MOD]
11/18/2025, 10:31 AMdata class Player(...): Comparable<Player> by compareFields(this::score, this::name)Huib Donkers
11/18/2025, 10:42 AMcompareFields needs to have a reference to this (Player) in order to do the comparison (compareTo(other)), but this isn't available when calling compareFields.
You can easily make the comparator you want: compareBy(Player::score, Player::name), but I haven't found a way to make that into a comparable to delegate to or something.Youssef Shoaib [MOD]
11/18/2025, 10:47 AMinterface ComparableByComparator<T: ComparableByComparator<T>>: Comparable<T> {
val comparator: Comparator<T>
override fun compareTo(other: T) = comparator.compare(this as T, other) // the unsafe cast is annoying, I know
}
and then you can do:
data class Player(...): ComparableByComparator<Player> {
override val comparator = comparatorByFields(Player::name, Player::score)
}
Personally, I tend to use Comparator instead of Comparable anyways, especially because it plays nicely with context parameters.
Or you can just do:
data class Player(...): Comparable<Player> {
override fun compareTo(other: Player) = compareFields(other, Player::name, Player::score)
}
Which has the advantage of being `inline`able, so it's zero-costDerek Peirce
11/18/2025, 10:57 AMcompareFields method, either through IntelliJ autofill or by a Google search, but such a method would work, with the more direct delegation being slightly preferable for being far shorter (just three words for comparing by two fields, as opposed to eleven for writing out the full method calling compareFields).Youssef Shoaib [MOD]
11/18/2025, 10:58 AMDerek Peirce
11/18/2025, 11:00 AMfun <T: Any> T.compareFields(other: T, vararg fields: T.() -> Comparable<*>): Int {
for (field in fields) {
val a = this.field() as Comparable<Any>
val b = other.field()
val comparison = a.compareTo(b)
if (comparison != 0) {
return comparison
}
}
return 0
}Youssef Shoaib [MOD]
11/18/2025, 11:03 AMinline fun <T: Comparable<T>> T.compareWithDiscriminator(other: T, discriminator: () -> Int) = compareTo(other).let { if (it == 0) discriminator() else it }
inline fun <T, A: Comparable<A>, B: Comparable<B>> T.compareFields(other: T, a: T.() -> A, b: T.() -> B) = a().compareWithDiscriminator(other.a()) { b().compareTo(other.b()) }
for an inline implementation, but yours likely works as well!Derek Peirce
11/18/2025, 11:04 AMYoussef Shoaib [MOD]
11/18/2025, 11:05 AMYoussef Shoaib [MOD]
11/18/2025, 11:30 AMclass ComparingDsl<T>(val first: T, val second: T, val raise: Raise<Int>) {
inline fun <A: Comparable<A>> field(value: T.() -> A) =
first.value().compareTo(second.value()).let { if (it != 0) raise.raise(it) }
}
inline fun <T> T.comparing(other: T, block: ComparingDsl<T>.() -> Unit) = merge {
block(ComparingDsl(this@comparing, other, this))
0
}
interface Comparing<T: Comparing<T>>: Comparable<T> {
fun ComparingDsl<T>.compare()
override fun compareTo(other: T) = (this as T).comparing(other) { compare() }
}
data class Player(val name: String, val score: Int): Comparing<Player> {
override fun ComparingDsl<Player>.compare() {
field(Player::name)
field(Player::score)
}
}
You could even change field to something more concise, like +, although that loses the `inline`ing.
Your compareFields thus becomes:
fun <T: Any> T.compareFields(other: T, vararg fields: T.() -> Comparable<*>) = comparing(other) {
for (f in fields) field { f() as Comparable<Any> }
}Derek Peirce
11/19/2025, 6:08 AMRaise compare to the efficiency of a more standard early return?