Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala `view`: `force` is not a member of `Seq`

It appears that applying map and filter somehow converts a view into a Seq. The documentation contains this example:

> (v.view map (_ + 1) map (_ * 2)).force
res12: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22)  

But if I do something similar, I get an error:

> val a = Array(1,2,3)
> s.view.map(_ + 1).map(_ + 1).force
<console>:67: error: value force is not a member of Seq[Int]

It seems that if I map over an Array view more than once the SeqView becomes a Seq.

> a.view.map(_+1)
res212: scala.collection.SeqView[Int,Array[Int]] = SeqViewM(...)
> a.view.map(_+1).map(_+1)
res211: Seq[Int] = SeqViewMM(...)

I suspect this behavior may have something to do with Array being a mutable collection, as I cannot replicate this behavior with List or Vector. I can however filter the Array view as many times as I like.

like image 879
Justin Raymond Avatar asked Oct 17 '22 22:10

Justin Raymond


1 Answers

Pro tip: when debugging implicits in the REPL, reify from reflect is your friend.

scala> import reflect.runtime.universe.reify
scala> import collection.mutable._ // To clean up reified exprs
scala> reify(a.view.map(_ + 1).map(_ * 2))
Expr[Seq[Int]](Predef.intArrayOps($read.a).view.map(((x$1) => x$1.$plus(1)))(IndexedSeqView.arrCanBuildFrom).map(((x$2) => x$2.$times(2)))(Seq.canBuildFrom))

By design, IndexedSeqView.arrCanBuildFrom produces not another IndexedSeqView, but a plain old SeqView. However, from then on you'd expect SeqViews to remain so. In order for that to happen, the CBF passed to the second map should be SeqView.canBuildFrom, but for some reason we are getting the one from Seq. Now that we know the issue, let's pass SeqView.canBuildFrom manually and dissect the error.

scala> a.view.map(_ + 1).map(_ * 2)(collection.SeqView.canBuildFrom)
<console>:??: error: polymorphic expression cannot be instantiated to expected type;
 found   : [A]scala.collection.generic.CanBuildFrom[collection.SeqView.Coll,A,scala.collection.SeqView[A,Seq[_]]]
    (which expands to)  [A]scala.collection.generic.CanBuildFrom[scala.collection.TraversableView[_, _ <: Traversable[_]],A,scala.collection.SeqView[A,Seq[_]]]
 required: scala.collection.generic.CanBuildFrom[scala.collection.SeqView[Int,Array[Int]],Int,?]
       a.view.map(_ + 1).map(_ * 2)(collection.SeqView.canBuildFrom)
                                                       ^

Ok, so this is not a bug in implicit resolution or the compiler or anything; it is the library at fault, as the compiler is able to give us a good reason for failing here.

scalac requires the second type param to CBF be Int or a supertype of it here, and since the one we give it takes any A, we're good. The third one is unknown, so it can also be anything, also good. Therefore, the problem is in the first.

scala> implicitly[collection.SeqView[Int, _] <:< collection.TraversableView[_, _]]
<function1>

This narrows the issue down to Array[Int] <: Traversable[_]. And there it is. Arrays are not Traversable, so they fail here and are forced to become Seqs by the CBF in Seq.

To fix this, SeqView would need to have a arrCanBuildFrom like IndexedSeqView does. This is a bug in the library. This doesn't relate to Array being mutable; it's really because Array is not really a collection (it doesn't implement Traversable) and has to be shoehorned in.

like image 141
HTNW Avatar answered Oct 21 '22 05:10

HTNW