We have extensions functions: first, firstOrNull, firstNotNullOf, firstNotNullOfOrNull
but not: firstOf, firstOfOrNull
Why is that the case?
For example with my implementation of firstOfOrNull like:
inline fun <T, R> Iterable<T>.firstOfOrNull(
transform: (T) -> R,
predicate: (R) -> Boolean
): R? {
for (element in this) {
val result = transform(element)
if (predicate(result)) {
return result
}
}
return null
}
Using it in an example situation, compared to doing the same with firstOrNull and firstNotNullOfOrNull:
val sortedValueList = listOf("1", "2", "3", "4", "5")
fun transformation(s: String) = s.toInt() * 10
listOf(
{ i: Int -> i > 25 },
{ i: Int -> i > 100 },
).forEach { predicate ->
listOf(
//0 firstOfOrNull
sortedValueList.firstOfOrNull(::transformation, predicate),
//1 firstOrNull (does 1 extra transformation compared to the other two)
sortedValueList.firstOrNull {
predicate(transformation(it))
}.let { it?.let { transformation(it) } },
//2 firstNotNullOfOrNull
sortedValueList.firstNotNullOfOrNull {
val t = transformation(it)
if (predicate(t)) t else null
}
).forEachIndexed { i, it -> println("$i: $it") }
}
(Outputs)
0: 30
1: 30
2: 30
0: null
1: null
2: null
I think there is a point in having the firstOfOrNull function as it is cleaner.
Or did I miss something?
Thanks for reading through!
public inline fun <T> Iterable<T>.firstOrNull(predicate: (T) -> Boolean): T? {
for (element in this) if (predicate(element)) return element
return null
}
@kotlin.internal.InlineOnly
public inline fun <T, R : Any> Iterable<T>.firstNotNullOfOrNull(transform: (T) -> R?): R? {
for (element in this) {
val result = transform(element)
if (result != null) {
return result
}
}
return null
}
first functions would return after finding the first element
list.map(...).first(...) would perform mapping for all the entries, then find the first match
I see your point. I’m just not sure if it makes sense to duplicate all filtering operators like first, last, filter, takeWhile and many more just to provide lazy map+filter functionality. Not to mention combinations like e.g. flatMap+filter.
From a purely aesthetic position, functions that take multiple lambdas in Kotlin are kind of ugly imo; having the final lambda be outside the parentheses results in a nice look for Kotlin, but if you have multiple lambdas, then at least one has to be inside parentheses and it looks weird. For example:
listOf(1, 2, 3, 4, 5)
.firstOfOrNull({ it * 2}) { it > 5 }
vs
listOf(1, 2, 3, 4, 5)
.map { it * 2 }
.firstOrNull { it > 5 }
Plus as broot said, it’s really not that much extra code to transform your collection to a sequence and then do map + firstOrNull.
fun <T, R: Any> Iterable<T>.lastNotNullOfOrNull(transform: (T) -> R?): R? {
var last: R? = null
for (element in this) {
val result = transform(element)
if (result != null) {
last = result
}
}
return last
}
but then similarly again, if this shouldn’t exist then why should its counterpart firstNotNullOfOrNull exist
I can’t help but to feel like there is something I am missing, something that I didn’t consider beside the readability or aesthetic issues
Now that I’ve looked at firstNotNullOfOrNull and firstNotNullOf, I think those are kind of weird functions to have… a simple map that returns a null followed by filterNotNull and then firstOrNull would work… I guess it’s a bit shorter, but it seems like a pretty niche use case to have a built-in language function for this. Maybe a lot of the Kotlin devs need it so they added it as a standard function? Or… I think Kotlin language features are often requested by the community, yeah? Maybe there’s a ticket for adding those standard functions, which would have explanations from people as to why they wanted those functions.
That is the nice thing about Kotlin, if you find such a function valuable, you can add it. It is not likely to make it in stdlib because there are other ways to do it and because they generally try to have only one lambda parameter in a function call as it is more readable.