How can we define retroactive trait conformance

Hello Scala enthusiasts!

I am looking for a way to define a retroactive conformance to a trait. In other words, I’d like to make an existing type conform to a new trait. For example, imagine that I declare this:

trait BitSet {
  def isSet(position: Int): Bool
}

Is there a way I can make Long conform to BitSet?

My goal is to be able to define generic algorithms in terms of a new trait (e.g., BitSet) and use them on already existing types (e.g. Long). I suppose I could wrap instances in new classes conforming to my trait, but then I would be compelled to wrap/unwrap these representations all the time.

My apologies if the terms I’m using are a little foreign. Don’t hesitate to request clarifications as necessary.

This is what typeclasses are for, and one of the reasons many folks prefer them to traditional OO style in many situations. Unlike inheritance-based behavior, a typeclass can be adapted to any appropriate class, whether new or legacy.

So basically, instead of defining an OO trait BitSet, you would create a typeclass BitSet[T], whose instance for a given type T provides the implementation of isSet() for T. You can then couple that with a syntax adapter (an implicit class in Scala 2; I forget the right syntax for Scala 3), and you wind up with something that you call much like the OO version, but is more flexible.

2 Likes

Thanks a lot for your reply!

I’ve got this far with your suggestion:

trait BitSet[T] {
  extension (x: T) {
    def isSet(position: Int): Boolean
  }
}

given BitSet[Long] with
  extension (x: Long) def isSet(position: Int) = (x & (1 << position)) != 0

def someGreatAlgorithm[T: BitSet](x: T) = x.isSet(0)

That is great!

Now let’s say I want my trait to have an abstract type member. How can I define that type in the instance of my type class? For example:

trait BitSet[T] {
  type Slice
  extension (x: T) {
    def slice(range: Range): Slice
  }
}

class LongSlice()

given BitSet[Long] with
  // probably something missing here
  extension (x: Long) def slice(range: Range) = LongSlice()

Someone else will have to chime in here – I haven’t done enough Scala 3 to know the right approach. In Scala 2 it would just be another member in the instance:

implicit object LongBitSet = new BitSet[Long] {
  type Slice = LongSlice
  def slice(x: Long, range: Range): Slice = LongSlice()
}

But I’m not sure offhand how to best express that type implementation in Scala 3. (Implicits were heavily rewritten between 2 and 3, to reduce boilerplate and make the system more usable.)

1 Like

You can simply implement the type member in the given definition:

given BitSet[Long] with
  type Slice = LongSlice
  extension (x: Long) def slice(range: Range) = LongSlice()

But be aware that Slice will only be known to be LongSlice in places in the code where you statically know that you have this specific BitSet[Long] instance. E.g. take this pretty silly example:

def foo(using b: BitSet[Long]): b.Slice = ???

In the method body of foo you can’t know whether or not the BitSet[Long] instance you are “using” is the instance that you defined with type Slice = LongSlice. However if you call val slice = foo then slice should have type LongSlice because the compiler knows which instance it passed into foo.

1 Like

That’s embarrassing. I thought I had tried that. Thanks a lot!

Yes, makes perfect sense!