List.copy? Useful?


#1

Hi!

Playing with M4 and it seems very solid. :slight_smile:

I noticed there isn’t any copy methods for collection classes, isn’t that something that should be useful to have?
Also, maybe the method name should be changed from copy() to change()?

Take a look at this code and say what you think:

import java.util.ArrayList

data class Person(val firstName: String, val lastName: String)

fun Person.asMarried(newLastName: String) = this.copy(lastName = newLastName)

fun Person.asSmurf() = this.copy(lastName = “Smurf”)

fun main(args: Array<String>) {
  val persons = listOf(
           Person(“Tom”, “Jones”),
           Person(“David”, “Bowie”),
           Person(“David”, “Hasselhoff”),
           Person(“James”, “Brown”))

  val persons1 = persons.copy(1…2, { it.copy(lastName = “Smurf”) })
  val persons2 = persons1.copy(1…2, { it.asSmurf() })
  val persons3 = persons1.copy(1, { it.asSmurf() })

  println(persons1)
  println(persons2)
  println(persons3)
}

fun <A> List<A>.copy(f: (A) -> A): List<A> = this.map { f(it) }

fun <A> List<A>.copy(index: Int, f: (A) -> A): List<A> = this.copy(index…index, f)

fun <A> List<A>.copy(range: IntRange, f: (A) -> A): List<A> {
  return this.indexedMap {(i, v) ->
  if (i in range) f(v) else v
  }
}

fun <A, B> Iterable<A>.indexedMap(f: (Int, A) -> B): List<B> {
  val answer = ArrayList<B>()
  var nextIndex = 0
  for (e in this) {
  answer.add(f(nextIndex, e))
  nextIndex++
  }
  return answer
}

Output would be:

[Person(firstName=Tom, lastName=Jones), Person(firstName=David, lastName=Smurf), Person(firstName=David, lastName=Smurf), Person(firstName=James, lastName=Brown)] [Person(firstName=Tom, lastName=Jones), Person(firstName=David, lastName=Smurf), Person(firstName=David, lastName=Smurf), Person(firstName=James, lastName=Brown)] [Person(firstName=Tom, lastName=Jones), Person(firstName=David, lastName=Smurf), Person(firstName=David, lastName=Smurf), Person(firstName=James, lastName=Brown)]


#2

Maybe do you need List#subList?


#3

What do you think about the following code?

package subList

fun List<*>.get(range: IntRange) = subList(range.start, range.end + 1)

fun main(args : Array<String>) {
  val l = arrayListOf(1, 2, 3, 4)
  println("${l[1…2]}")

  (l[1…2] as MutableList<*>).clear()
  println("$l")
}

//Don't use. (For details see KT-2979) //fun MutableList<*>.get(range: IntRange) = subList(range.start, range.end)

#4

That looks nice, but it's not connected to what I posted about :)

I’m talking about the .copy()-method that is autogenerated on data classes and how one might want to have a similar method on collections of data classes.

In my example, persons.copy(1…2, { something… }) means that we want to change the items in the persons list and create a new list that is identical, except for the items 1…2.


#5

The stdlib contains some extension function for copying collections (all data, without transform) -- toList, toSet, etc. You can use subList for partial copying and You can use map for copying with transform. I think that's enough.


#6

Have you looked into Clojure? Idioms like this seems to be very successful for them.


#7

No, I don't have clojure skills. Can show examples(preferably with real use cases) and / or links? Thanks.

In any case your functions have the misleading name.
Maybe they should be have one of the following signatures?

fun <A> List<A>.partialMap(range: IntRange, functor: (A) -> A): List<A>
fun <A> List<A>.mapIn(range: IntRange, functor: (A) -> A): List<A>

// for more common cases:
fun <A> List<A>.partialMap(condition: (A) -> Boolean, functor: (A) -> A): List<A>
fun <A> List<A>.mapIf(condition: (A) -> Boolean, functor: (A) -> A): List<A>


#8

Yes these are actually map functions, the implementation could also be:

``

fun <A> List<A>.map(index: Int, f: (A) -> A): List<A> = this.map(index…index, f)
fun <A> List<A>.map(range: IntRange, f: (A) -> A): List<A> =  this.withIndices().map {if(it.first in range) f(it.second)  else it.second}
val persons1 = persons.map(1…2, { it.copy(lastName = “Smurf”) })
val persons2 = persons1.map(1…2, { it.asSmurf() })
val persons3 = persons1.map(1, { it.asSmurf() })

#9

I think these signatures will be mislead users, because users may think that the resulting collection contains ONLY mapped elements from range. And i think that first function(with index) is unnecessary.