A library that makes some Scala 2.13 APIs available on Scala 2.11 and 2.12, facilitating cross-building Scala 2.13 and 3.0 code on older versions
—
Enhanced iterator methods and efficient size comparison utilities for collections.
implicit class IteratorExtensionMethods[A](private val self: Iterator[A]) extends AnyVal {
def nextOption(): Option[A]
def sameElements[B >: A](that: TraversableOnce[B]): Boolean
def concat[B >: A](that: TraversableOnce[B]): TraversableOnce[B]
def tapEach[U](f: A => U): Iterator[A]
}def nextOption(): Option[A]Safe version of next() that returns None when the iterator is exhausted instead of throwing an exception.
Usage:
val iterator = List(1, 2, 3).iterator
iterator.nextOption() // Some(1)
iterator.nextOption() // Some(2)
iterator.nextOption() // Some(3)
iterator.nextOption() // None (instead of NoSuchElementException)def sameElements[B >: A](that: TraversableOnce[B]): BooleanCompare two iterators/traversables element by element for equality.
Usage:
val iter1 = List(1, 2, 3).iterator
val iter2 = Vector(1, 2, 3).iterator
val iter3 = List(1, 2, 4).iterator
iter1.sameElements(iter2) // true
iter1.sameElements(iter3) // false
// Works with any TraversableOnce
val list = List(1, 2, 3)
list.iterator.sameElements(list) // truedef concat[B >: A](that: TraversableOnce[B]): TraversableOnce[B]Concatenate the iterator with another TraversableOnce.
Usage:
val iter1 = List(1, 2, 3).iterator
val iter2 = List(4, 5, 6)
val combined = iter1.concat(iter2)
combined.toList // List(1, 2, 3, 4, 5, 6)def tapEach[U](f: A => U): Iterator[A]Apply a function to each element for side effects while preserving the iterator.
Usage:
val iterator = List(1, 2, 3, 4, 5).iterator
val processed = iterator
.tapEach(x => println(s"Processing: $x"))
.filter(_ % 2 == 0)
.tapEach(x => println(s"Even number: $x"))
processed.toList
// Output:
// Processing: 1
// Processing: 2
// Even number: 2
// Processing: 3
// Processing: 4
// Even number: 4
// Processing: 5
// Result: List(2, 4)class SizeCompareOps(private val it: Traversable[_]) extends AnyVal {
def <(size: Int): Boolean
def <=(size: Int): Boolean
def ==(size: Int): Boolean
def !=(size: Int): Boolean
def >=(size: Int): Boolean
def >(size: Int): Boolean
}Efficient size comparison operations that can short-circuit for better performance, especially with large or infinite collections.
def <(size: Int): Boolean // Tests if collection size is less than given size
def <=(size: Int): Boolean // Tests if collection size is less than or equal to given size
def ==(size: Int): Boolean // Tests if collection size equals given size
def !=(size: Int): Boolean // Tests if collection size is not equal to given size
def >=(size: Int): Boolean // Tests if collection size is greater than or equal to given size
def >(size: Int): Boolean // Tests if collection size is greater than given sizeUsage:
import scala.collection.compat._
val list = List(1, 2, 3, 4, 5)
// Size comparisons - more efficient than list.size comparisons
list.sizeIs < 10 // true
list.sizeIs <= 5 // true
list.sizeIs == 5 // true
list.sizeIs != 3 // true
list.sizeIs >= 5 // true
list.sizeIs > 2 // true
// Works with any Traversable
val set = Set("a", "b", "c")
set.sizeIs == 3 // true
set.sizeIs > 5 // falseimplicit class TraversableExtensionMethods[A](private val self: Traversable[A])
extends AnyVal {
def iterableFactory: GenericCompanion[Traversable]
def sizeCompare(otherSize: Int): Int
def sizeIs: SizeCompareOps
def sizeCompare(that: Traversable[_]): Int
}def sizeCompare(otherSize: Int): Int
def sizeCompare(that: Traversable[_]): IntCompare collection size with an integer or another collection, returning negative/zero/positive like Ordering.
Returns:
Usage:
val list1 = List(1, 2, 3)
val list2 = List(4, 5)
list1.sizeCompare(5) // negative (3 < 5)
list1.sizeCompare(3) // zero (3 == 3)
list1.sizeCompare(2) // positive (3 > 2)
list1.sizeCompare(list2) // positive (3 > 2)
list2.sizeCompare(list1) // negative (2 < 3)def sizeIs: SizeCompareOpsGet a SizeCompareOps instance for the collection to perform size comparisons.
implicit class SeqExtensionMethods[A](private val self: Seq[A]) extends AnyVal {
def lengthIs: SizeCompareOps
}def lengthIs: SizeCompareOpsSize comparison operations specifically for sequences (using length terminology).
Usage:
val seq = Seq(1, 2, 3, 4)
seq.lengthIs < 10 // true
seq.lengthIs == 4 // true
seq.lengthIs > 5 // falseSize comparison operations can short-circuit, providing better performance than computing the full size:
val largeList = (1 to 1000000).toList
// Inefficient - computes full size
largeList.size > 100 // Processes all 1M elements
// Efficient - stops after 101 elements
largeList.sizeIs > 100 // Only processes 101 elementsSize operations work efficiently with lazy collections:
import scala.collection.compat.immutable._
val infiniteStream = LazyList.from(1)
// These operations terminate quickly
infiniteStream.sizeIs > 5 // true (checks first 6 elements)
infiniteStream.sizeIs == 10 // false (checks first 11 elements)
// This would never terminate
// infiniteStream.size // Don't do this!import scala.collection.compat._
def processDataBatch[T](data: Iterable[T], maxBatchSize: Int): List[List[T]] = {
if (data.sizeIs <= maxBatchSize) {
List(data.toList)
} else {
data.grouped(maxBatchSize).toList
}
}
// Efficient - stops counting after maxBatchSize + 1 elements
val batches = processDataBatch(largeDataset, 1000)def validateCollectionSizes[T](
required: Traversable[T],
optional: Traversable[T]
): Either[String, Unit] = {
if (required.sizeIs == 0) {
Left("Required items cannot be empty")
} else if (required.sizeIs > 100) {
Left("Too many required items (max 100)")
} else if (optional.sizeIs > 50) {
Left("Too many optional items (max 50)")
} else {
Right(())
}
}def processUserData(users: Iterator[User]): List[ProcessedUser] = {
users
.tapEach(user => println(s"Processing user: ${user.id}"))
.filter(_.isActive)
.tapEach(user => println(s"Active user: ${user.id}"))
.take(100) // Limit processing
.tapEach(user => println(s"Within limit: ${user.id}"))
.map(processUser)
.toList
}
case class User(id: String, isActive: Boolean, name: String)
case class ProcessedUser(id: String, name: String, processedAt: Long)
def processUser(user: User): ProcessedUser =
ProcessedUser(user.id, user.name, System.currentTimeMillis())def safeIteratorProcessing[T](iterator: Iterator[T]): List[T] = {
val buffer = scala.collection.mutable.ListBuffer[T]()
while (iterator.hasNext) {
iterator.nextOption() match {
case Some(element) => buffer += element
case None => // Iterator was exhausted between hasNext and next
}
}
buffer.toList
}
// Alternative using tap for logging
def loggedIteratorProcessing[T](iterator: Iterator[T]): List[T] = {
iterator
.tapEach(element => println(s"Processing: $element"))
.toList
}def selectSmallestCollection[T](collections: List[Traversable[T]]): Option[Traversable[T]] = {
collections match {
case Nil => None
case head :: tail =>
Some(tail.foldLeft(head) { (smallest, current) =>
if (current.sizeCompare(smallest) < 0) current else smallest
})
}
}
val lists = List(
List(1, 2, 3),
List(4, 5, 6, 7, 8),
List(9, 10)
)
val smallest = selectSmallestCollection(lists) // Some(List(9, 10))def createBalancedBatches[T](
items: Traversable[T],
minBatchSize: Int,
maxBatchSize: Int
): List[List[T]] = {
if (items.sizeIs < minBatchSize) {
List(items.toList) // Single batch if too few items
} else {
val iterator = items.toIterator
val batches = scala.collection.mutable.ListBuffer[List[T]]()
while (iterator.hasNext) {
val batch = iterator.take(maxBatchSize).toList
if (batch.sizeIs >= minBatchSize || !iterator.hasNext) {
batches += batch
} else {
// Combine small final batch with previous batch
if (batches.nonEmpty) {
val lastBatch = batches.remove(batches.length - 1)
batches += (lastBatch ++ batch)
} else {
batches += batch
}
}
}
batches.toList
}
}Seq types, lengthCompare is used when available for O(1) comparisonstapEach creates a new iterator that applies the side effect function during iterationInstall with Tessl CLI
npx tessl i tessl/maven-org-scala-lang-modules--scala-collection-compat