curious case of putting override modifier when overriding an abstract method in Scala

This is a translation of Scalaで抽象メソッドをoverrideする際にoverride修飾子を付けるべきかどうかの是非 by Kenji Yoshida (@xuwei_k), a Scalaz committer.

First, a quote from Programming in Scala, 2nd ed. p. 192:

Scala requires [override] modifier for all members that override a concrete member in a parent class. The modifier is optional if a member implements an abstract member with the same name.

In this post, we’ll discuss this “The modififier is optional.” Since overriding an existing method with implementation requires override modifier, and failure to do so would result to a compiler error, there’s not much to talk about for that case. We’ll focus on whether one should put override modifier or not in the case of overring an abtract method. I don’t think there’s going to be any difference in Scala version, but let’s assume the latest stable 2.10.3.

“The modifier is option” is correct in face value, but I found seen much discussion in the book or on the web1 on which one should be used, or if there is any difference in putting override or not.

For a long while, I used to think: It’s true that “the modifier is optional,” but

However, I realized a rare, but an actual case that not putting override might be preferable, so I’m writing this now. This occurs in so-called diamond inheritance.

Let’s see the code first. This will compile:

trait A{
  def foo: Int
}

trait B extends A{
  override def foo = 1
}

trait C extends A{
  override def foo = 2
}

trait D extends C with B
// Later mixin has precedence, so B's implementation is used

This, on the other hand would result to a compiler error:

trait A{
  def foo: Int
}

trait B extends A{
  def foo = 1
}

trait C extends A{
  def foo = 2
}

trait D extends C with B
/**
error: trait D inherits conflicting members:
  method foo in trait C of type => Int  and
  method foo in trait B of type => Int
(Note: this can be resolved by declaring an override in trait D.)
       trait D extends C with B
             ^
*/

In short, this is a case in which there’s a possibility of a diamond inheritance, and when one would prefer to explicitly override conflicts instead of depending on the mixin order of the traits.

You might ask “how often would would such case arise?” but I see them. In Scalaz.

In Scalaz,

override def map[A,B](fa: F[A])(f: A => B): F[B] = traversal[Id](Id.id).run(fa)(f)

Further discussion require a bit of an internal knowledge of Scalaz:

In case other typeclass instances are required, by conventions in Scalaz, we define private traits to share the implementation. Since each typeclass require different typeclasses3, we end up with defining enormous amount of private traits.

For instance, here’s from OneOr as of 7.1.0-M4. https://github.com/scalaz/scalaz/blob/v7.1.0-M4/core/src/main/scala/scalaz/OneOr.scala#L112:

private sealed trait OneOrFunctor[F[_]]
    extends Functor[({type λ[α] = OneOr[F, α]})#λ] {
  implicit def F: Functor[F]

  override def map[A, B](fa: OneOr[F, A])(f: A => B): OneOr[F, B] =
    fa map f
}

private sealed trait OneOrTraverse[F[_]]
  extends OneOrFunctor[F] with OneOrFoldable[F] with Traverse[({type λ[α] = OneOr[F, α]})#λ] {

  implicit def F: Traverse[F]

  override def traverseImpl[G[_]: Applicative,A,B](fa: OneOr[F, A])(f: A => G[B]) =
    fa traverse f

  override def foldMap[A, B](fa: OneOr[F, A])(f: A => B)(implicit M: Monoid[B]) =
    fa.foldMap(f)
}

OneOrTraverse is inheriting OneOrFunctor to use the implementation of map overridden by OneOrFunctor. Or at least, that’s the intention.

But in actuality, because with Traverse is at the end, so the implementation from Traverse is being used. In other words, inheriting OneOrFunctor is rendered pointless. It’s only by accident that map implementation from Traverse is included. So I just fixed it: https://github.com/scalaz/scalaz/commit/db3082f1895

It’s not exactly a critical bug if the map implementation from Traverse is being used. But in most cases we can provide more efficient implementation of map than that of Traverse.

This logic applies for Applicative and Monad, which also provide default implementation of map.

Had it been the case that the map implementation of Traverse did not have override modifier, the map implementation of OneOrFunctor and Traverse would conflict, and it would result in a compiler error.

Thus, in the case of diamond inheritance, whether or not putting override modifier would make the difference of it being a compiler error or not.

In current Scalaz:

This is convenient, but on the other hand:

This is a subtle conundrum. The topic of default implementation for typeclass has both pros and cons to it, that there’s no clear panacea. Scalaz ended up in the current implementation after taking various issues into consideration, so I continue to forge on making very minor improvements like this, while fighting suble conundrums like this.

A feature of marking the implementations of abtract methods override, but make it a compiler error when they conflict instead of using the mixin order of the traits may be useful. But personally speaking, I hate the very spec of semantics depending on the mixin orders, so I wish that went away from the spec altogether.


  1. Please let me know if there’s any material covering this topic whether it’s a book or online. ↩︎

  2. Applicative also overrides map, so similar problem arises. ↩︎

  3. For instance, to define Functor for OneOr the type parameter F needs to be a Functor, but to define Traverse for OneOr F needs to be a Traverse↩︎