Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent Chain instances being backed by mutable or lazy Seq #4169

Merged
merged 8 commits into from
May 31, 2022

Conversation

bplommer
Copy link
Contributor

@bplommer bplommer commented Apr 8, 2022

This addresses two issues:

  • Changes Chain.Wrap to wrap immutable.Seq instead of Seq, which until 2.13 could be mutable.
  • When converting from IterableOnce, explicitly convert to Vector. Previous use of .iterator.toSeq can return a lazy Seq, creating race conditions - in 2.12 it returns Stream. In 2.13 it returns List, but this isn't guaranteed by the return type.

I haven't attempted to prevent Chain from wrapping a LazyList or Stream passed directly to the constructor.

@bplommer bplommer marked this pull request as ready for review April 8, 2022 10:32
@satorg
Copy link
Contributor

satorg commented Apr 8, 2022

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Contributor

@johnynek johnynek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this!

Nice safety fix.

Comment on lines 15 to 18
s match {
case mut: MutSeq[A] => fromMutableSeq(mut)
case imm: ImSeq[A] => fromImmutableSeq(imm)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.mutable

Then 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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I very much feel the same way about this.

Comment on lines 20 to 24
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)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

@johnynek johnynek Apr 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
}

@armanbilge
Copy link
Member

@bplommer polite bump, seems just a few fixes and then this is mergeable :) thanks!

@armanbilge armanbilge added this to the 2.8.0 milestone May 16, 2022
@armanbilge armanbilge linked an issue May 17, 2022 that may be closed by this pull request
@bplommer
Copy link
Contributor Author

@bplommer polite bump, seems just a few fixes and then this is mergeable :) thanks!

Completely forgot about this! Will do soon :)

@armanbilge armanbilge requested review from satorg, armanbilge and johnynek and removed request for armanbilge May 28, 2022 15:02
@armanbilge armanbilge merged commit a5ade30 into typelevel:main May 31, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Chain in Cats built for Scala 2.12 can keep a mutable collection
4 participants