Prevent Chain instances being backed by mutable or lazy Seq#4169
Prevent Chain instances being backed by mutable or lazy Seq#4169armanbilge merged 8 commits intotypelevel:mainfrom
Chain instances being backed by mutable or lazy Seq#4169Conversation
|
Just to clarify: what are the race conditions you mentioned? |
| case s: ImSeq[A] => fromImmutableSeq(s) // pay O(1) not O(N) cost | ||
| case s: MutSeq[A] => fromMutableSeq(s) | ||
| case notSeq => | ||
| fromImmutableSeq(notSeq.toVector) // toSeq could return a Stream, creating potential race conditions |
There was a problem hiding this comment.
let's add a more complete comment here. I don't know the race conditions you are referring to, and future comment readers might also be confused.
I think toVector is fine even if there is no race conditions because Vector is very memory efficient (due to internally using Array).
johnynek
left a comment
There was a problem hiding this comment.
Thanks for taking this!
Nice safety fix.
| s match { | ||
| case mut: MutSeq[A] => fromMutableSeq(mut) | ||
| case imm: ImSeq[A] => fromImmutableSeq(imm) | ||
| } |
There was a problem hiding this comment.
Not an objection but rather just a suggestion about the style.
I personally don't like such renames on imports, because they tend to introduce new not-well known names. Moreover, Scala Docs suggests a convention about it:
https://docs.scala-lang.org/overviews/collections/overview.html
I.e. you could import it in this way:
import scala.collection.immutable
import scala.collection.mutableThen use it in the code:
s match {
case seq: mutable.Seq[A] => fromMutableSeq(mut)
case seq: immutable.Seq[A] => fromImmutableSeq(imm)
}That looks way clearer and easier to read, doesn't it?
There was a problem hiding this comment.
Yup, I very much feel the same way about this.
| private def fromImmutableSeq[A](s: ImSeq[A]): Chain[A] = { | ||
| if (s.isEmpty) nil | ||
| else if (s.lengthCompare(1) == 0) one(s.head) | ||
| else Wrap(s) | ||
| } |
There was a problem hiding this comment.
This implementation is identical in both Scala 2.12 and 2.13+ versions. I wonder – does it make sense to make it shared to avoid unnecessary duplication?
There was a problem hiding this comment.
Eh, I don't think it's worth the mental indirection when reading the code. It's just a few lines, it's fine.
| def fromSeq[A](s: Seq[A]): Chain[A] = | ||
| s match { | ||
| case mut: MutSeq[A] => fromMutableSeq(mut) | ||
| case imm: ImSeq[A] => fromImmutableSeq(imm) |
There was a problem hiding this comment.
actually, Seq isn't sealed, so in principle there could be other subclasses.
Can we instead do:
s match {
case imm: ImSeq[A] => fromImmutableSeq(imm)
case _ =>
if (s.isEmpty) Empty
else if (s.lengthCompare(1) == 0) Singleton(s.head)
else Wrap(s.toVector)
}|
@bplommer polite bump, seems just a few fixes and then this is mergeable :) thanks! |
Completely forgot about this! Will do soon :) |
This addresses two issues:
Chain.Wrapto wrapimmutable.Seqinstead ofSeq, which until 2.13 could be mutable.IterableOnce, explicitly convert toVector. Previous use of.iterator.toSeqcan return a lazy Seq, creating race conditions - in 2.12 it returnsStream. In 2.13 it returnsList, but this isn't guaranteed by the return type.I haven't attempted to prevent
Chainfrom wrapping aLazyListorStreampassed directly to the constructor.