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.
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.