How to maintain the subtype passed as an argument to a function?

I defined this extension function which runs only when a collection is not empty.

    fun <T> Collection<T>.runIfNotEmpty(block: (Collection<T>) -> Unit) =
        takeIf { isNotEmpty() }?.let { block(this) }

Now I can use it with List and Set so I am happy. The problem is that the argument to the lambda let is always Collection and not the concrete List or Set I call this on. For example.

// it has type Collection and not LIST
emptyList<String>().runIfNotEmpty { it -> println(it) }; 

// it has type Collection and not SET
emptyList<String>().runIfNotEmpty { it -> println(it) };

Is there a way to express to the compiler that I want the original sub-type of the collection to be preserved or will I have to provide a copy pasted method for each subtype I am interested in ?

I realise that normal functions work this way as well but I am just wondering if I can do something about it…

Something like this should suffice here:

inline fun <C : Collection<*>> C.runIfNotEmpty(block: (C) -> Unit) {
	if (isNotEmpty()) block(this)
}
3 Likes

Wonderful, thanks a lot. I did the following but now I see I don’t need the extra type parameter !
Why do we need the inline keyword ? Seems to work without it…

fun <X, T : Collection<X>> T.runIfNotEmpty(block: (T) -> Unit) =
    takeIf { isNotEmpty() }?.let { block(this) }

The inline keyword was indeed not necessary. It was just a little optimization for lambdas. :wink:

1 Like