Iterating with: operator fun <T> Iterable<T>.invoke(f: T.(Int) -> Unit)


#1

This function:

inline operator fun <T> Iterable<T>.invoke(f: T.(Int) -> Unit) {
	var i = 0
	for (item in this) item.f(i++)
}

Can be used to make for-each loops with a really short syntax:

class Person(val name: String)

fun example() {

	val array = arrayListOf(Person("Tom"), Person("Sam"))

	//traditional syntax
	for ((index, value) in array.withIndex()) {
		print("Iterating ${value.name} with index $index")
	}

	//new syntax
	array {
		print("Iterating $name with index $it")
	}
}

The problem comes across when somebody uses an “inline operator fun T.invoke(…a function)” for other purposes like an apply or a run, then, the language becomes confusing, it’s like having a “part of the language” that is different for every project.

My suggestion is including this function “as a language expression”, for making for-each loops, and removing the “operator fun invoke”, at least if the function has only 1 parameter and it’s also a function.


#2

There is already a function that does what you want. You just have to type a bit more: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/for-each-indexed.html


#3

I know, but this post is about this function:

inline operator fun Something.invoke( a function )

For determining where and how to use it (if it might be used), if it should be a “for loop” or any other thing, and
even if it has no index, or in the case “T.() -> Unit” is replaced with “(T) -> Unit”, this thread is about that
syntactic sugar itself not about indexed for loops.


#4

I wonder why do you think that your usage of operator invoke is more important than any other usage someone could think of, so that we should bake it into the language and prohibit those other usages?


#5

I said for-each because it is one of the most used statements, just for that.

Why prohibit other usages?

Simple, because many people using that syntactic sugar for different purposes as a language operator won’t be a problem UNTIL they meet together, then, both will want to use the function as they did before, but only one function is possible in many cases.

Example cases:

  • Coder 1 uses it as a first() and coder 2 uses it as firstOrNull(), then, they should decide which one to use, if they declare both, the compiler will say “conflicting overloads”.

  • Coder 1 uses it as a for each and coder 2 uses is as a with (for any, not just for Iterables), then, it will compile, but in many cases, the compiler won’t know what function to choose, saying “cannot choose among the following candidates without completing type interfaces”.

Even without been coding the same project, if someone just see a project, for example, on GitHub, he won’t know what does that mean, so he will need to research.

Just imagine Java if “for”,“while” and “if” meant different things across different projects, and you would have to find out what do they mean before start understanding the code, the same happens here, this is becoming a language feature but for different purposes and no conventions, that’s why I think that JetBrains should define the function purpose, and prohibit the rest use cases.


#6

It basically boils down to that such a feature does not need to be in the standard library since all you are really doing is shortenting:

array.forEachIndexed { ... }

to

array { ... }

If you find this useful then the beauty of Kotlin is you can define it in your code (it’s only a one liner). Note that you can shorten your code down to:

inline operator fun <T> Iterable<T>.invoke(f: T.(Int) -> Unit)
   = forEachIndexed { (i, value) -> value.f(i) }

#7

A case however could be made that there should be overloads of forEachIndexed that instead of taking a 2 parameter function (int, T) -> Unit instead takes an extension function T.(int) -> Unit.