It would be nice to be able to implement `Comparab...
# language-proposals
d
It would be nice to be able to implement
Comparable
by delegation. For instance,
Copy code
data class Player(val name: String, val score: Int): Comparable<Player> by (score, name)
would be short for adding:
Copy code
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
.
y
I think you could probably already do something like:
Copy code
data class Player(...): Comparable<Player> by compareFields(this::score, this::name)
h
@Youssef Shoaib [MOD] I wanted to suggest something like that, but I can't get it to work. The comparable returned by
compareFields
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.
y
Ooo, good catch! Yeah, interface delegation is kinda broken (Breslav said it's one of his design regrets). How about something like:
Copy code
interface 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:
Copy code
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:
Copy code
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-cost
d
I'm not seeing a
compareFields
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
).
y
Oh sorry, I should've clarified lol. I meant you can implement such a method 😅
d
Ah, I think I have a rough implementation, then, something like:
Copy code
fun <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
}
y
Copy code
inline 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!
d
I was about to say, it would want inline lambdas to work for varargs, which doesn't seem to have made much progress since the last time I asked about it.
y
Yeah the issue hasn't seen much progress. More compile-time stuff would be really useful, but I can see why it takes time to design. The workaround that I go for is to just make like 22 overloads and call it a day.
I whipped up a Raise-based DSL that makes this a bit nicer (untested):
Copy code
class 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:
Copy code
fun <T: Any> T.compareFields(other: T, vararg fields: T.() -> Comparable<*>) = comparing(other) {
  for (f in fields) field { f() as Comparable<Any> }
}
d
How does the efficiency of
Raise
compare to the efficiency of a more standard early return?