How to copy a list in Scala

Maxime picture Maxime · Sep 18, 2009 · Viewed 16.9k times · Source

I want to shallow copy a list in Scala.

I wanted to do somehing like:

val myList = List("foo", "bar")
val myListCopy = myList.clone

But the clone method is protected.

Answer

Daniel C. Sobral picture Daniel C. Sobral · Sep 19, 2009

Here's a non-answer: don't do that. A List is immutable, so there's absolutely no point in copying one.

Let's consider a few operations:

val list = List(1,2,3)
val l1 = 0 :: list
val l2 = "a" :: list

Neither l1 nor l2 are altering list, but they both create new lists that reference list.

Let's explain this in detail. The constructor List(1,2,3) is creating three elements, and using a singleton object as well. Specifically, it is instantiating these elements:

::(3, Nil)
::(2, reference to the previous element)
::(1, reference to the previous element)

And Nil is a singleton object. What the identifier list actually points to is that last element.

Now, when you assign 0 :: list to l1, you are instantiating one new object:

::(0, reference to ::(1, etc))

Of course, since there's a reference to list, you can think of l1 as a list of four elements (or five, if you count Nil).

Now l2 isn't even of the same type of list, but it ALSO references it! Here:

::("a", reference to ::(1, etc))

The important point about all these objects, though, is that they can't be changed. There are no setters, nor any methods that will change any of their properties. They'll be forever having the same values/reference in their "head" (that's what we call the first element), and the same references in their "tail" (that's what we call the second element).

However, there are methods that look like their are changing the list. Rest assured, however, that they are creating new lists. For instance:

val l3 = list map (n => n + 1)

The method map creates a completely new list, of the same size, where new element may be computed from a corresponding element in list (but you might ignore the old element as well).

val l4 = l2 filter (n => n.isInstanceOf[Int])

While l4 has the same elements as list (but a different type), it is also a completely new list. The method filter creates a new list, based on a rule you pass to tell it what elements go in and what don't. It doesn't try to optimize in case it could return an existing list.

val l5 = list.tail

This does not create a new list. Instead, it simply assigns to l5 an existing element of list.

val l6 = list drop 2

Again, no new list created.

val l7 = list take 1

This, however, creates a new list, precisely because it can't change the first element of list so that its tail points to Nil.

Here's a few additional implementation details:

  • List is an abstract class. It has two descendants, the class :: (yes, that's the name of the class), and the singleton object Nil. List is sealed, so you can't add new subclasses to it, and :: is final, so you can't subclass it.

  • While you can't do anything to change a list, it uses mutable state internally in some operations. This helps with performance, but it is localized so that no program you write can ever detect it, or suffer consequences from it. You can pass lists around as you wish, no matter what the other functions do with them, or how many threads are using them concurrently.