Pier Bezuhoff
Pier Bezuhoff

Reputation: 97

Selecting function reference before application: problem with type inference

I want to choose function reference before applying it to arguments, but Kotlin cannot infer its type.

Suppose I have a class

class Circle(val radius: Float)

and a function

fun sortByRadius(circles: MutableList<Circle>, ascending: Boolean) {
    if (ascending)
        circles.sortBy { it.radius }
    else
        circles.sortByDescending { it.radius }
}

I want too rewrite this function body to something like this:

circles.(
    if (ascending) MutableList<Circle>::sortBy
    else MutableList<Circle>::sortByDescending
) { it.radius }

but it does not work. Also I found out that

(MutableList<Circle>::sortBy)(circles) { it.radius }

almost works, Kotlin just can not infer Float type of radius; so I wonder how to specify it. Then we could write

(if (ascending) MutableList<Circle>::sortBy
    else MutableList<Circle>::sortByDescending)(circles) { it.radius }

Upvotes: 1

Views: 174

Answers (2)

Alexey Romanov
Alexey Romanov

Reputation: 170909

Nice question, and it doesn't seem to be possible (using function references). I've tried to specify expected type explicitly:

val f: MutableList<Circle>.((Circle) -> Float) -> Unit = 
    if (ascending) MutableList<Circle>::sortBy else MutableList<Circle>::sortByDescending

but the error message says

Type inference failed. Please try to specify type arguments explicitly.

and the grammar doesn't allow it:

callableReference
  : (receiverType? '::' (simpleIdentifier | 'class'))
  ;

i.e. :: can only be followed by an identifier or class. Which is a strange hole because Java grammar does allow type parameters before method name.

Bound references don't help either:

(circles::sortBy) { it.radius } // doesn't compile

Not even explicit type works, which quite surprised me:

val f: ((Circle) -> Float) -> Unit = circles::sortBy 

but this does:

val f: ((Circle) -> Float) -> Unit = { circles.sortBy(it) }

So ultimately you can write (parentheses around lambdas required)

val f: ((Circle) -> Float) -> Unit = 
  if (ascending) ({ circles.sortBy(it) }) else ({ circles.sortByDescending(it) })
f { it.radius }

but probably not want to!

Upvotes: 1

amatkivskiy
amatkivskiy

Reputation: 1291

Do not know whether I fully get your point but here what I wrote for your case:

fun main() {
    val circles = mutableListOf(
        Circle(.1f), Circle(.5f), Circle(3f)
    )

    val sortBy: (MutableList<Circle>, selector: (Circle) -> Float) -> Unit = { list, selector ->
        list.sortBy(selector)
    }

    val sortByDescending: (MutableList<Circle>, selector: (Circle) -> Float) -> Unit = { list, selector ->
        list.sortByDescending(selector)
    }

    val ascending = false
    (if (ascending) sortBy else sortByDescending)(circles) {
        it.radius
    }
    // Or maybe more clear way
    //(if (ascending) sortBy else sortByDescending).invoke(circles) {
    //    it.radius
    //}
}

Hope this will help you.

Upvotes: 0

Related Questions