PhilBa
PhilBa

Reputation: 732

Trait enforcing impl of method for subtypes

Is it possible in Scala to enforce the implementation of a typed trait for subclasses with the type of the subclass? Ie. I'd like to define a trait which forces all subtypes to implement Ordered for only the concrete subtype. So I want to ensure that every implementation TraitImpl of the trait ATrait implements Ordered[TraitImpl]. Below is a non working example to illustrate what I am trying to do.

trait ATrait extends Ordered[_ <: ATrait] {
}

class TraitImpl extends ATrait {
    override def compare(that: TraitImpl): Int = ???
}

Upvotes: 1

Views: 127

Answers (4)

jwvh
jwvh

Reputation: 51271

@Sheng is right, F-bounded polymorphism is probably the way to go, but the code offered is incomplete in that it will allow the following.

trait ATrait[T <: ATrait[T]] extends Ordered[T]

class B extends ATrait[B] {
  override def compare(that: B): Int = ???
}
class C extends ATrait[B] {
  override def compare(that: B): Int = ???
}

This bleeding of B into C is not what you want. A more complete implementation would be as such.

trait ATrait[T <: ATrait[T]] {
  self: T with Ordered[T] =>
}

class B extends ATrait[B] with Ordered[B] {
  override def compare(that: B): Int = ???
}
class C extends ATrait[C] with Ordered[C] {
  override def compare(that: C): Int = ???
}

With this you can't use Ordered[B] in the definition of C.

update

Or, a slightly more concise rendition as @Sheng has kindly pointed out.

trait ATrait[T <: ATrait[T]] extends Ordered[T] {self: T =>}

class B extends ATrait[B] {
  def compare(that: B): Int = ???
}
class C extends ATrait[C] {
  def compare(that: C): Int = ???
}

Upvotes: 2

flavian
flavian

Reputation: 28511

I think if you "step outside the box" you could fair better with a context bound. Fortunately, Scala offers you two flavours of implementing order, the second of which being through implicits.

trait A extends Ordered[T <: A[T]] {
  def compare(that: T)(implicit ordering: Ordering[T]): Int = {
    ordering.compare(this, that)
  }
}

class TraitImpl extends A[TraitImpl]

Now you can insert the Ordering at call site and save yourself the trouble of constantly overriding things in any form, wherever you end up calling compare that's where that implicit will be required.

You can control your default ordering imports with some kind of package object, so you would offer a much better importing experience to your users or colleagues.

Upvotes: 0

rstar
rstar

Reputation: 1006

You may create a F-bounded type:

trait ATrait[T <: ATrait[T]] extends Ordered[T] {
}

class TraitImpl extends ATrait[TraitImpl] {
    override def compare(that: TraitImpl): Int = ???
}

Update: as pointed out by @jwvh, this is not unbreakable as class TraitImpl extends ATrait[B] also compiles, given B is another subclass of ATrait. Please refer to his anwser for a more complete solution.

Upvotes: 0

L.Lampart
L.Lampart

Reputation: 755

I think that the most elegant solution would be usage of self-annotation :

trait ATtrait[T] { self: Ordered[T] =>
}

Every class implementing this trait has to also implement ordered, and you can use ordered methods inside ATrait.

Upvotes: 0

Related Questions