I have two helper functions that take either a `Li...
# codereview
a
I have two helper functions that take either a
List<T:Any?>
or a
T:Any?
, with slightly different implementations. The goal is for the caller not to have to care whether the param is a value or collection of values (SQL interpolation stuff) What’s a good way to allow these to have the same name? currently I have
Copy code
inline fun <reified T : Any> Statement.helperList(param: Collection<T>?, stmt: (Param<*>) -> Statement) = TODO("thing that has to know T")
inline fun <reified T : Any> Statement.helper(param: T?, stmt: (Params<*>) -> Statement) = TODO("Same thing but handled a differnt way since its not a collection"
Currently if I give them the same name,
helper
will be called even if the argument is a collection, because it also matches
Any
. Is there a way to constrain T so that it’s “Not collection” somehow?
j
Did you confirm the behavior you're describing? Since List is more specific than
Any
, the overload with
List
should be chosen first
a
You are right. I must have simplified too much when converting to an example here. Please hang on
Aha, the 2nd argument which is a lambda has different signatures in the two methods. Which leads to overload ambiguity, not List being treated as Any as I thought. Updated example accordingly
So I guess what happens is it cannot determine the
stmt
lambda signature at call site, based on
param
’s type. Which i suppose would be unreasonable to expect anyway 😛
However if i use
T: Comparable<T>
and
T: Collection<T>
it’s able to resolve. But this again would require us to implement Comparable on various objects, just for this useage 🤔
j
I see. So no, there is no way (to my knowledge) to define "anything but a collection". That said, it doesn't seem like a big problem that the caller has to know about this: the lambda parameter is different, and the lambda body is provided by the caller, so they have to know what to do with it anyway
d
Maybe possible with some sort of currying?
Hmm, I this almost works:
Copy code
data object Bar

data object A
data object B

inline fun <reified T : Any> Bar.foo(items: Iterable<T?>): ((A) -> Any) -> Any = { it(A) }

inline fun <reified T : Any> Bar.foo(item: T?): ((B) -> Any) -> Any = { it(B) }

fun main() {
    println(Bar.foo(listOf(1, 2, 3))({ it }))
    println(Bar.foo(1)({ it }))
}
Slightly cleaner yet more verbose syntax:
Copy code
data object A
data object B

class Curried<T, R>(val onNext: (T) -> R) {
    infix fun handle(next: T) = onNext(next)
}

inline fun <reified T : Any> foo(items: Iterable<T>) = Curried<(A) -> Any, Any> { it(A) }
inline fun <reified T : Any> foo(item: T) = Curried<(B) -> Any, Any> { it(B) }

fun main() {
    foo(listOf(1, 2, 3)) handle { println("$it for a list") }
    foo(1) handle { println("$it for a single item") }
}
r
What about using varargs? So you could do:
Copy code
inline fun <reified T : Any> Statement.helper(vararg param: T?, stmt: (Params<*>) -> Statement)
you can then call the method like this
Copy code
Statement("bla").helper(Params("ABC"), Params("123")) {...}
Statement("bla").helper(listOf(Params("ABC"), Params("123"))) {...}