I've read
I think I have a bit of a handle on canBuildFrom
.
But then I look at the source for TraversableView
, and I see this:
object TraversableView {
class NoBuilder[A] extends Builder[A, Nothing] {
def +=(elem: A): this.type = this
def iterator: Iterator[A] = Iterator.empty
def result() = throw new UnsupportedOperationException("TraversableView.Builder.result")
def clear() {}
}
type Coll = TraversableView[_, C] forSome {type C <: Traversable[_]}
implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, TraversableView[A, Traversable[_]]] =
new CanBuildFrom[Coll, A, TraversableView[A, Traversable[_]]] {
def apply(from: Coll) = new NoBuilder
def apply() = new NoBuilder
}
}
How does TraversableView
even work like this? It seems like there is nothing happening here (NoBuilder
seems aptly named).
Could someone explain (1) the function that NoBuilder
plays here and (2) how map
, filter
, etc. can still work?
The NoBuilder
exists so that clients can treat Scala views as if they were normal collections when transforming them. It is a dummy implementation that allows TraversableView
to extend Traversable
and call methods like map
without knowing if the collection is a view or a normal collection.
When you call map
, flatMap
or scanLeft
(called transformer operations) on a Scala collection, a CanBuildFrom
implicit argument is automatically resolved. The CanBuildFrom
object is an abstract factory for Builder
objects.
Most Scala collections use Builders
to add elements with +=
and produce a new collection by calling result
on the buidler. E.g. given a builder object b
, map
does this:
def map[S, That](f: T => S)(implicit cbf: CanBuildFrom[Repr, S, That]) = {
val b: Builder[S, That] = cbf(this)
for (x <- this) b += f(x)
b.result
}
Transformer operations on views do not instantiate a new collection. Instead, they create a lazy view. For example, map
does something like this:
def map[S, That](f: T => S)(implicit cbf: CanBuildFrom[Repr, S, That]) = new TraversableView {
def foreach[U](forFunc: T => U): Unit = for (x <- self) forFunc(f(x))
}
Note that a map
on a view has the same signature, but does not really call +=
and result
on the builder. Thus, the NoBuilder
used in views does not really need to store elements or return a collection, it is just a dummy whose methods are never called.
The same signature allows writing this in client code:
def foo(xs: Traversable[Int]) = xs.map(_ + 1)
foo(0 until 100)
foo((0 until 100).view)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With