The Scala language provides to ways to implement the collections. One is strict and other is non-strict or lazy.
Whenever, the instance of a collections in scala(except for the Streams) is created, it creates the strict version of the collection which means memory is allocated at the same time.
A simple example of strict collection is:
val list = List(1,2,3,4,5,6,7,8,9,10)
list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
The above code, the memory would be allocated for the List immediately.
But whenever a view in the collection is created, it makes that collection as a lazy collection which means memory allocation would not be done at the time of initialization, instead, it would take place whenever they are actually accessed or some transformations are applied on them.
val listView = List(1,2,3,4,5,6,7,8,9,10).view
listView: scala.collection.SeqView[Int,List[Int]] = SeqView(...)
Here, the type of
SeqView[Int, List[Int]], which makes a lazy collection.
The signature of
SeqView is explained as:
Intis the type of elements in the view.
List[Int]is the output we get when it is to be taken back to strict-collection.
Views take very little space in memory, it contains only the definition, not the copy of all the data that it represents.
Now, lets take some transformations on lazy collections and see how the type changes.
val view = List(1,2,3,4,5,6,7,8,9,10).view
view: scala.collection.SeqView[Int,List[Int]] = SeqView(...)
val sqList = view.map(n => n*n)
sqList: scala.collection.SeqView[Boolean,Seq[_]] = SeqViewM(...)
The first line is self explainable and its type also. But, the second line, i.e., when applying a map transfoamtion on the view, the type of
The trailing ‘M’ indicates that the view encapsulates a map operation.
Similarly, applying reverse on a view:
val reverseList = view.reverse
reverseList: scala.collection.SeqView[Int,List[Int]] = SeqViewR(...)
SeqViewR, where trailing ‘R’ means that a reverse operation has been applied on some view.
Applying, two map functions back to back yields
SeqViewMM type, similarly, using filter on view yields
This is how, the transformations are applied on views. But, how do we finally see the result out from a view ? Well, that’s simple.
force method on the view.
And, immediately the REPL will output as:
List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
Views and Streams are different from each other. Streams are not processed until any transformations are applied on it. But, views are lazily evaluations and processed only when forced to do so.
val streams = Stream.range(1,10)
streams: scala.collection.immutable.Stream[Int] = Stream(1, ?)
val views = List.range(1,10).view
views: scala.collection.SeqView[Int,List[Int]] = SeqView(...)
val streamRightResp = streams.takeRight(2)
streamRightResp: scala.collection.immutable.Stream[Int] = Stream(1, 2, 3, 4, 5, 6, 7, 8, 9)
val viewRightResp = views.takeRight(2)
viewRightResp: scala.collection.SeqView[Int,List[Int]] = SeqView(...)
The above code snippet, when
takeRight(2) is applied, all the elements before that index (i.e 10-2=8) are now in memory, but this was not in case with views. If one wants to access the element at nth index from Stream, then all the element before the nth index has to be in-memory.
Views can be used when:
- a large collection is not needed to be in memory.
- There is no need of intermediate collections, which are created every time a transformation is applied on strict collections.