Waqar Haider
Waqar Haider

Reputation: 971

Kotlin: generic method and for loop asking for iterator()

This is a simple generic method,and passing the values the args in for loop is causing an error saying:

for-loop range must have and iterator() method

fun main(args: Array<String>) {
    val arr: IntArray = intArrayOf(1,2,3,4)
    val charA: CharArray = charArrayOf('a','b','c','d')

    printMe(arr)
    printMe(charA)

}

fun <T>printMe(args: T){
   for (items in args){
        println(items)
    }
}

how do i make it iterate throught the values for both char[] and array

Upvotes: 3

Views: 1953

Answers (3)

Ilya
Ilya

Reputation: 23125

for-loop in Kotlin works by convention, looking statically for an operator member named iterator which must return something that can be iterated, i.e. something that has in turn operator members next and hasNext.

operator modifier on such members is required to specify that the member is to satisfy some convention, namely the iteration convention.

Since args is of type T and there is no iterator member in every possible type T, it can't be iterated readily.

However you can provide an additional parameter to printMe, which knows how to get an iterator out of an instance of T, and then use it to obtain an iterator and iterate it:

fun main(args: Array<String>) {
    val arr: IntArray = intArrayOf(1,2,3,4)
    val charA: CharArray = charArrayOf('a','b','c','d')

    printMe(arr, IntArray::iterator)
    printMe(charA, CharArray::iterator)

}

fun <T> printMe(args: T, iterator: T.() -> Iterator<*>) {
    for (item in args.iterator()) {
        println(item)
    }
}

Here T.() -> Iterator<*> is a type which denotes a function with receiver. Instances of that type can be invoked on T as if they were its extensions.

This snippet works because the iterator returned has itself an operator extension function Iterator<T>.iterator() = this which just returns that iterator, thus allowing to loop through the iterator with a for-loop.

Upvotes: 5

Michael Anderson
Michael Anderson

Reputation: 73490

This is actually a little bit subtle.

The key problem is that variable arr is of type IntArray and IntArray is not derived from Array. Similarly while IntArray has an iterator() function it does not implement Iterable<>.

The same occurs for for CharArray variable.

In fact IntArray and CharArray and Array<T> don't seem to have any common base class or interface other than Any. So you're either stuck passing an object and doing a typecheck in printMe, or using overloading.

A type-checking version would look like

printMe(args:Any) {
   if(args is IntArray) {
      for(item in args) {
         println(item)
      }
   } else if (args is CharArray) {
     for(item in args) {
        println(item)
     }
   } else {
     println("Not an IntArray or a CharArray")
   }
}

Overloading would look like this

printMe(args:IntArray) {
  for(item in args) {
    println(item)
  }
}

printMe(args:CharArray) {
  for(item in args) {
    println(item)
  }
}

IMO overloading is the better option since you can't end up passing an object you cant handle by mistake.

Upvotes: 2

syntagma
syntagma

Reputation: 24324

The problem is that the compiler does not know you are passing an Array and T can be of any type.

One way to fix this would be using is operator:

fun <T>printMe(args: T){
    if(args is Array<*>) {
            for (items in args) {
                println(items)
            }
    }
}

Upvotes: 0

Related Questions