1

Given the following method extensions (JsonResult is just a wrapper with some additional fields):

fun <T, R> T.toJson(transform: (T) -> R) = JsonResult(transform(this))

fun <T, R> List<T>.toJson(transform: (T) -> R) = JsonResult(this.map(transform))

fun <T, R> Page<T>.toJson(transform: (T) -> R) = JsonResult(this.content.map(transform)

When I try to call any of the above methods, either from a list or an object that is not a list, I get a compiler error with the message Cannot choose among the following candidates without completing type inference which I expect

import org.springframework.data.domain.Page

val person = Person()
person.toJson { } // compiler error

val people = List<Person>()
people.toJson { } // compiler error

val pageablePeople = Page<Person>
pageablePeople.toJson { } 

I wonder if there is any other (idiomatic?) way to declare those functions without changing their names.

1 Answers1

0

The reason, you're getting overload resolution errors is because a receiver of type List<T> matches both, fun <T, R> T.toJson and fun <T, R> List<T>.toJson.

Extension functions with an unbound receiver can be problematic, because they will appear in every autocompletion suggestion and because they are always candidates in overload resolution.

I would suggest making the unbound version either a top-level function or putting it in an object.

Example:

fun <T, R> toJson(t: T, transform: (T) -> R) = JsonResult(transform(t))

fun <T, R> List<T>.toJson(transform: (T) -> R) = JsonResult(map(transform))

fun <T, R> Page<T>.toJson(transform: (T) -> R) = JsonResult(content.map(transform))

fun main(args: Array<String>) {
    val person = Person()
    toJson(person) {}

    val people = listOf<Person>()
    people.toJson { }

    val pageablePeople = Page<Person>()
    pageablePeople.toJson { }
}

Additionally, in the case of classes that you have control over, you can make toJson an instance function which always wins agains extension functions in overload resolution.

class Page<T>(val content: List<T>) {
    fun <R> toJson(transform: (T) -> R) = JsonResult(content.map(transform))
}
Kirill Rakhman
  • 42,195
  • 18
  • 124
  • 148
  • Thank you Kirill for taking the time to respond. I'm aware of the inability of the compiler to resolve the type but I wish I could say T where T is direct child of Any and not a Collection or any other type. I like the workaround of passing the unbound object as argument though. As a side note, I have no control over some of the classes. –  Jan 29 '18 at 12:15
  • The only issue I see with `fun toJson(t: T, transform: (T) -> R) = JsonResult(transform(t))` is that is not consistent with the other extensions and callees should remember to call toJson(obj) –  Jan 29 '18 at 12:27
  • I understand your concerns. The only alternative is renaming one of the overloads. – Kirill Rakhman Jan 29 '18 at 15:14