scalascala-collectionsscala-2.13

How to convert this SetLike collection from Scala 2.12 to 2.13?


I have this simple immutable Long-based bitset that contains the Card case class. Unfortunately, with the Scala 2.13 collections revamp, it does not compile anymore.

I have looked at the BitSet implementation in Scala 2.13, but it is very verbose. How would I go about converting this class while keeping the code clean and succint? Is there a demo example of such data structure that I could build upon?

import scala.collection.{GenSet, GenTraversableOnce, SetLike}

case class Card(ord: Int)

case class Deck(data: Long) extends Set[Card] with SetLike[Card, Deck] {
    def iterator = new Iterator[Card] {
        var cur = data

        def next = {
            val res = java.lang.Long.numberOfTrailingZeros(cur)
            cur &= ~(1L << res)
            Card(res)
        }

        def hasNext = cur != 0
    }

    override def empty: Deck = Deck.empty
    override def size: Int = java.lang.Long.bitCount(data)
    override def contains(card: Card): Boolean = (data & (1L << card.ord)) > 0
    override def +(card: Card): Deck = Deck(data | (1L << card.ord))
    override def -(card: Card): Deck = Deck(data & ~(1L << card.ord))

    private def toBits(deck: GenTraversableOnce[Card]): Long = deck match {
        case deck: Deck => deck.data
        case _ =>
            var data = 0L
            for (card <- deck) data |= 1L << card.ord
            data
    }

    override def ++(deck: GenTraversableOnce[Card]): Deck = Deck(data | toBits(deck))
    override def &(deck: GenSet[Card]): Deck = Deck(data & toBits(deck))
    override def diff(deck: GenSet[Card]): Deck = Deck(data & ~toBits(deck))
}

object Deck {
    val empty: Deck = Deck(0)
    def of(deck: Iterable[Card]): Deck = empty ++ deck
}

Solution

  • Should be something like this; I've commented the notable steps

    // SetLike has been sorta superseded by SetOps. Notice the 3rd type parameter -
    // that's the return constructor of e.g. deck.map(Some(_))
    case class Deck(data: Long) extends Set[Card] with SetOps[Card, Set, Deck] {
      
      // Minimum definition of set is these two, instead of symbolic names
      override def incl(card: Card): Deck = Deck(data | (1L << card.ord))
      override def excl(card: Card): Deck = Deck(data & ~(1L << card.ord))
    
      def iterator = new Iterator[Card] {
        var cur = data
    
        def next() = {
          val res = java.lang.Long.numberOfTrailingZeros(cur)
          cur &= ~(1L << res)
          Card(res)
        }
    
        def hasNext = cur != 0
      }
    
      override def empty: Deck = Deck.empty
      override def size: Int = java.lang.Long.bitCount(data)
      override def contains(card: Card): Boolean = (data & (1L << card.ord)) > 0
    
      // These two are inherited from SetOps but neither their type nor the impl aligns with
      // what you want them to do. You have to re-override them to type as Deck instead of Set[Card]
      override protected def fromSpecific(coll: IterableOnce[Card]): Deck = Deck(toBits(coll))
      
      override protected def newSpecificBuilder: Builder[Card, Deck] =
        new Builder[Card, Deck] {
          var data = 0L
          def clear(): Unit = data = 0
          def result(): Deck = Deck(data)
          def addOne(elem: Card): this.type = {
            data |= (1L << elem.ord)
            this
          }
        }
    
      // I kept this as is but you can probably replace it with newSpecificBuilder
      // if you want to keep things more DRY - you could use
      // `newSpecificBuilder.addAll(deck).result()` in fromSpecific
      private def toBits(deck: IterableOnce[Card]): Long = deck match {
        case deck: Deck => deck.data
        case _ =>
          var data = 0L
          for (card <- deck.iterator) data |= 1L << card.ord
          data
      }
    
      
      // concat is the only non-final method - union and | just delegate to that.
      override def concat(deck: IterableOnce[Card]): Deck = Deck(data | toBits(deck))
      
      // this needs a set of unknown-mutability. It has kinda replaced GenSet
      override def diff(deck: scala.collection.Set[Card]): Deck = Deck(data & ~toBits(deck))
    }