Case classes can be seen as plain and immutable data-holding objects that should exclusively depend on their constructor arguments.
This functional concept allows us to
- use a compact initialization syntax (
Node(1, Leaf(2), None))
)
- decompose them using pattern matching
- have equality comparisons implicitly defined
In combination with inheritance, case classes are used to mimic algebraic datatypes.
If an object performs stateful computations on the inside or exhibits other kinds of complex behaviour, it should be an ordinary class.
First, they are all non-strict. That has a particular mathematical meaning related to functions, but, basically, means they are computed on-demand instead of in advance.
Stream
is a lazy list indeed. In fact, in Scala, a Stream
is a List
whose tail
is a lazy val
. Once computed, a value stays computed and is reused. Or, as you say, the values are cached.
An Iterator
can only be used once because it is a traversal pointer into a collection, and not a collection in itself. What makes it special in Scala is the fact that you can apply transformation such as map
and filter
and simply get a new Iterator
which will only apply these transformations when you ask for the next element.
Scala used to provide iterators which could be reset, but that is very hard to support in a general manner, and they didn't make version 2.8.0.
Views are meant to be viewed much like a database view. It is a series of transformation which one applies to a collection to produce a "virtual" collection. As you said, all transformations are re-applied each time you need to fetch elements from it.
Both Iterator
and views have excellent memory characteristics. Stream
is nice, but, in Scala, its main benefit is writing infinite sequences (particularly sequences recursively defined). One can avoid keeping all of the Stream
in memory, though, by making sure you don't keep a reference to its head
(for example, by using def
instead of val
to define the Stream
).
Because of the penalties incurred by views, one should usually force
it after applying the transformations, or keep it as a view if only few elements are expected to ever be fetched, compared to the total size of the view.
Best Answer
In general, all 6 fold functions apply a binary operator to each element of a collection. The result of each step is passed on to the next step (as input to one of the binary operator's two arguments). This way we can cumulate a result.
reduceLeft
andreduceRight
cumulate a single result.foldLeft
andfoldRight
cumulate a single result using a start value.scanLeft
andscanRight
cumulate a collection of intermediate cumulative results using a start value.Accumulate
From LEFT and forwards...
With a collection of elements
abc
and a binary operatoradd
we can explore what the different fold functions do when going forwards from the LEFT element of the collection (from A to C):From RIGHT and backwards...
If we start with the RIGHT element and go backwards (from C to A) we'll notice that now the second argument to our binary operator accumulates the result (the operator is the same, we just switched the argument names to make their roles clear):
.
De-cumulate
From LEFT and forwards...
If instead we were to de-cumulate some result by subtraction starting from the LEFT element of a collection, we would cumulate the result through the first argument
res
of our binary operatorminus
:From RIGHT and backwards...
But look out for the xRight variations now! Remember that the (de-)cumulated value in the xRight variations is passed to the second parameter
res
of our binary operatorminus
:The last List(-2, 3, -1, 4, 0) is maybe not what you would intuitively expect!
As you see, you can check what your foldX is doing by simply running a scanX instead and debug the cumulated result at each step.
Bottom line
reduceLeft
orreduceRight
.foldLeft
orfoldRight
if you have a start value.Cumulate a collection of intermediate results with
scanLeft
orscanRight
.Use a xLeft variation if you want to go forwards through the collection.