Nathan Nathan - 3 months ago 8
Scala Question

Scala: Returning a mutable buffer from a function that returns a Seq

I want to get a better understanding of what is actually happening in this code when I convert from a Java List to a generic Scala Seq:

import scala.collection.JavaConverters._

def foo(javaList: java.util.List[String]): Seq[String] = {
val scalaMutableBuffer: mutable.Buffer[String] = javaList.asScala
scalaMutableBuffer
}

...

val bar = foo(someJavaList)


Am I understanding it correctly that, while
bar
is typed as a Seq[String], it's using a mutable buffer on an underlying level, potentially affecting the performance of Seq operations? Is Seq simply referring to the buffer through the constraints of the Seq trait, or is there an actual underlying conversion that goes on? Would it be best to think of the value
bar
contains as mutable or immutable?

Pardon how open-ended this question is, but I don't feel like I have a good idea of what's going on and I'd like to change that. For example, would there be any cases in which it would be preferable for me to convert
scalaMutableBuffer
toList
before returning from
foo
?

Thanks!

Answer

while bar is typed as a Seq[String], it's using a mutable buffer on an underlying level

You're right, the run-time value of bar is a mutable.ArrayBuffer[String] (as Buffer is a trait itself), and as Seq[+A] is a trait, you as a caller only get to see the "sequency" parts of the ArrayBuffer, although you can always cast it to a buffer via buffer.asInstanceOf[mutable.ArrayBuffer[String]] and then see the actual "internals" of the buffer.

potentially affecting the performance of Seq operations

When one exposes a Seq[A], you're exposing the "contract" that the underlying collection adheres to. At run-time, it will always be a concrete implementation. i.e., when we create a Seq via apply:

scala> Seq(1,2,3)
res0: Seq[Int] = List(1, 2, 3)

The concrete implementation is actually a List[Int].

Would it be best to think of the value bar contains as mutable or immutable?

The underlying implementation is mutable, but exposed via an immutable contract. This means that when you're operating on the buffer via the abstraction of the Seq trait, there are no mutable operations available to you as a caller.

For example, when we do:

scala> val bufferAsSeq: Seq[Int] = scala.collection.mutable.Buffer(1,2,3)
bufferAsSeq: Seq[Int] = ArrayBuffer(1, 2, 3)

scala> bufferAsSeq += 4
<console>:12: error: value += is not a member of Seq[Int]
       bufferAsSeq += 4

The abstraction guards us from letting the users invoke operations we don't desire them to do on the run-time type.

For example, would there be any cases in which it would be preferable for me to convert scalaMutableBuffer toList before returning from foo

I think this is primarily opinion based. If you don't feel the Seq trait, you can always have the concrete type be immutable. But remember, in order for someone to mutate the copy of the Seq, he has to check the run-time type and explicitly cast to it, and when you cast all bets are off.

Comments