How to append a list to an immutable list of lists


#1

Due to how "plus" is overloaded, I am unable to append a list to a list of lists. Code:

fun main(args: Array<String>) {   // Added extra typing to demonstrate problem   var someList: List<List<Int>> = listOf(emptyList())   println("Size: " + someList.size)   someList += emptyList<List<Int>>()   println("Size: " + someList.size) }

Any help is appreciated.


#2

As far as I can see `add()` and `plus()` are different methods. `plus()` is more like extending two collections (like `[].extend()` in Python or list concatenation in Scala with `++` or `:::`).

fun main(args: Array<String>) {
    var listA = listOf<List<Int>>(emptyList()).toArrayList()
    println(listA.size) // 1
    listA.add(emptyList())
    println(listA.size) // 2

    var listB = listOf<List<Int>>(emptyList())
    println(listB.size) // 1
    listB += listOf(emptyList())
    println(listB.size) // 2
}

Either "cast" to the MutableCollection interface (with `toArrayList()` or use `arrayListOf()` straight away) or extend the existing collection with another collection.


#3

There appears to be two issues here. First someList is of type List, a read-only container and should not support the operator += (plusAssign member function). To update a list you could use an ArrayList initialized with arrayListOf.

Second, the actual type arguments used in someList += emptyList<List<Int>>()
should specify the element type of the empty list eg emptyList<Int>(). Here is some working code:

 
import java.util.*

fun main(args: Array<String>) {
  // Added extra typing to demonstrate problem
  var someList: ArrayList<List<Int>> = arrayListOf(emptyList())
  println("Size: " + someList.size)
  someList.plusAssign(emptyList<Int>())
  //someList += emptyList<Int>()   // COMPILER ERROR: assignment operators ambiguity
  println("Size: " + someList.size)

  /***** ORIGINAL CODE:
  // Added extra typing to demonstrate problem
  var someList: List<List<Int>> = listOf(emptyList())
  println("Size: " + someList.size)
  someList += emptyList<List<Int>>()
  println("Size: " + someList.size)
  *****/
}


#4

I don't want to upcast my type or use a concrete collection type or use a mutable list. I want to append two immutable lists. This is just a demonstration...what if I wanted my method to take a List<List<int>>? As an exercise, please fill in the contents of the function:

fun appendList<T>(listOfLists: List<List<T>>, toAppend: List<T>): List<List<T>> { }

Of course both of the following work:

fun appendList<T>(listOfLists: List<List<T>>, toAppend: List<T>): List<List<T>> {   // Use spread operator   return listOf(*listOfLists.toTypedArray(), toAppend); }

fun appendList<T>(listOfLists: List<List<T>>, toAppend: List<T>): List<List<T>> {
  // Turn to mutable list
  val ret = listOfLists.toArrayList()
  ret.add(toAppend)
  return ret
}


But this is confusing to users who might expect + to work. Due to type erasure, + will always have quirks like this. I had to add the following extension:

operator fun <T, C : Collection<T>> List<C>.plus(toAppend: C): List<C> {
    val ret = java.util.ArrayList<C>(this.size + 1)
    ret.addAll(this)
    ret.add(toAppend)
    return ret
}

This fixes the problem but I promise this will be confusing for users as they start to use Kotlin more and more.


#5

1. Seems like Kotlin is falling back to `plus()` if no `plusAssign()` is implemented on the type. You are totally right, the compile shoud raise an error here.

  1. It looked like the Kotlin plugin infers the type of List&lt;Int&gt;.

#6

For me the `+` operator on a sequential type (String, List) concatenates it with another and returns a new object. So the LHS (left handside) and RHS of the operator should accept the same types.

>Due to type erasure, + will always have quirks like this.

Isn’t this the case for both actions (append and extend/concatenate)?


#7

Yes it is the case, it is unfair for me to mention type erasure here as it is unrelated to the problem.

“For me the + operator on a sequential type (String, List) concatenates it with another and returns a new object.” For me, + only adds a single item. If there needs to be a special case for + to concatenate two collections, it should be typed strictly enough that List<List<T>> + List<T> works as expected (or fails). This should be possible since overload resolution is performed statically.


#8

For #1, I disagree. I like the fact that "foo += bar" falls back to "foo = foo + bar" in cases where plusAssign is not present (i.e. immutable collections). As is common for languages with immutable collections (e.g. Scala), people will prefer var with immutable collections over val with mutable ones except if really concerned about performance. I would rather not have to type "foo = foo + bar" constantly.


#9

I agree, the result of overload resolution looks unexpected in this situation. I've opened an issue about it in our tracker: https://youtrack.jetbrains.com/issue/KT-9992