Can I think of Scala already support multiple-inheritance?

Many people told me that Scala does not support multiple inheritance, but recently I read the Scala language specification, I found that Scala may already support multiple inheritance.

For example, suppose we have two classes Window and Door, now we want to define the 3rd class called WindowDoor, the most direct way in Scala is to use traits instead of classes.

The difficult part is how to deal with conflicting members.

After reading the Scala language specification, I found that Scala has a feature called static super reference, i.e. super[C].x that may deal with this situation.

See following code:

trait Window {
  private[this] var _state: Boolean = false

  def state: Boolean = _state

  def state_=(value: Boolean): Unit = {
    _state = value
  }

  def open() = {
    println("Window open")
    _state = true
  }
}

trait Door {
  private[this] var _state: Boolean = false

  def state: Boolean = _state

  def state_=(value: Boolean): Unit = {
    _state = value
  }

  def open() = {
    println("Door open")
    _state = true
  }
}

trait WindowDoor extends Window with Door {
  override def state: Boolean = throw new Exception("WindowDoor state abandoned state")
  override def state_= (x : Boolean) = throw new Exception("WindowDoor state_= abandoned state")
  override def open() = {
    println("WindowDoor open")
    super[Door].open()
    super[Window].open()
  }

  def stateWindow: Boolean = super[Window].state

  def stateWindow_=(value: Boolean): Unit = {
    super[Window].state_=(value)
  }

  def stateDoor: Boolean = super[Door].state

  def stateDoor_=(value: Boolean): Unit = {
    super[Door].state_=(value)
  }
}

object WindowDoor {
  def create(s1 : Boolean, s2: Boolean) : WindowDoor = {
    val obj = new WindowDoor {}
    obj.stateWindow = s1
    obj.stateDoor = s2
    obj
  }
}

val windowDoor = WindowDoor.create(false, false)
windowDoor.open()
windowDoor.stateDoor
windowDoor.stateWindow

// Output:
// WindowDoor open
// Door open
// Window open

Note that

  1. I use factory method to create object instead of new, because trait cannot have any “class” parameters.

  2. Since I use traits instead of classes, the Window and Door even WindowDoor can be reused in the future.

BTW this feature does not mentioned in the book Programming in Scala.

In the Chapter 12.6 - Why not multiple inheritance?

Authors mentioned that

For example, imagine the following Scala-like code, in which super appears to be explicitly invoked on both Incrementing and Doubling:

// Multiple inheritance thought experiment
trait MyQueue extends BasicIntQueue
  with Incrementing with Doubling {
  def put(x: Int) = {
    Incrementing.super.put(x) // (Not real Scala)
    Doubling.super.put(x)
  }
}

According to the authors, the above code is just pseudo-code, it does not really exist in the Scala language.

But in fact, Scala supports this kind of explicitly invoking super-trait, right? (or Scala’s designers discourage the use of static super reference?)

super[Incrementing].put(x)
super[Doubling].put(x)

Another way to implement WindowDoor is to separate State “classes” and Behavior “classes”.

See following code:

trait IOpen {
  def open()
}

trait IOpenDefaultImpl extends IOpen {
  override def open() = {
    println("IOpenDefaultImpl open")
  }
}

trait WindowState {
  private[this] var _state: Boolean = false

  def state: Boolean = _state

  def state_=(value: Boolean): Unit = {
    _state = value
  }
}

trait WindowBehavior extends WindowState with IOpen {
  abstract override def open() = {
    println("WindowBehavior open")
    super.open()
    super[WindowState].state_=(true)
  }
}

trait DoorState {
  private[this] var _state: Boolean = false

  def state: Boolean = _state

  def state_=(value: Boolean): Unit = {
    _state = value
  }
}

trait DoorBehavior extends DoorState with IOpen {
  abstract override def open() = {
    println("DoorBehavior open")
    super.open()
    super[DoorState].state_=(true)
  }
}

trait WindowDoorState extends WindowState with DoorState {
  override def state: Boolean = throw new Exception("WindowDoorState state abandoned state")
  override def state_= (x : Boolean) = throw new Exception("WindowDoorState state_= abandoned state")

  def stateWindow: Boolean = super[WindowState].state

  def stateWindow_=(value: Boolean): Unit = {
    super[WindowState].state_=(value)
  }

  def stateDoor: Boolean = super[DoorState].state

  def stateDoor_=(value: Boolean): Unit = {
    super[DoorState].state_=(value)
  }
}

trait Window extends WindowState
  with IOpenDefaultImpl
  with WindowBehavior

object Window {
  def create(s : Boolean) : Window = {
    val obj = new Window {}
    obj.state = s
    obj
  }
}

trait Door extends DoorState
  with IOpenDefaultImpl
  with DoorBehavior

object Door {
  def create(s : Boolean) : Door = {
    val obj = new Door {}
    obj.state = s
    obj
  }
}

trait WindowDoor extends WindowDoorState
  with IOpenDefaultImpl
  with WindowBehavior
  with DoorBehavior

object WindowDoor {
  def create(s1 : Boolean, s2: Boolean) : WindowDoor = {
    val obj = new WindowDoor {}
    obj.stateWindow = s1
    obj.stateDoor = s2
    obj
  }
}

trait DoorWindow extends WindowDoorState
  with IOpenDefaultImpl
  with DoorBehavior
  with WindowBehavior

object DoorWindow {
  def create(s1 : Boolean, s2: Boolean) : DoorWindow = {
    val obj = new DoorWindow {}
    obj.stateWindow = s1
    obj.stateDoor = s2
    obj
  }
}

println("----test window----")
val window = Window.create(false)
window.open()
window.state

println("----test door----")
val door = Door.create(false)
door.open()
door.state

println("----test windowDoor----")
val windowDoor = WindowDoor.create(false, false)
windowDoor.open()
windowDoor.stateDoor
windowDoor.stateWindow

println("----test doorWindow----")
val doorWindow = DoorWindow.create(false, false)
doorWindow.open()
doorWindow.stateDoor
doorWindow.stateWindow

// Output
----test window----
WindowBehavior open
IOpenDefaultImpl open

----test door----
DoorBehavior open
IOpenDefaultImpl open

----test windowDoor----
DoorBehavior open
WindowBehavior open
IOpenDefaultImpl open

----test doorWindow----
WindowBehavior open
DoorBehavior open
IOpenDefaultImpl open

The code above did two things:

  1. Merge two State Traits to one State Trait and rename conflicting members by static super reference.

  2. Do stackable modifications on Behavior Traits by override and super.

Therefore, can I think of Scala already support multiple-inheritance?

If not, which multiple-inheritance features are still lacking in Scala?

Thanks.

PS: I’m sorry I know there is already a post about multiple-inheritance, but the system prompts me that the last reply to that thread was 8 months ago. It seems tends to let me create a new one.

1 Like

Scala does support multiple inheritance, albeit via traits, not classes. You can inherit multiple traits but only directly from a single class.

2 Likes

That’s an ancient version of the Programming in Scala. I guess Scala did not support the feature at that time.

It depends on the definition of “multiple-inheritance”. It seems multiple-inheritance with linearization is not considered as multiple-inheritance in the book. I guess the book just want to say linearization is better than multiple-inheritance without linearization.

2 Likes

After some research, I found that Scala may not fully support multiple inheritance.

See the next comments below.

In fact, even the fourth edition of the Programming in Scala, it still doesn’t mention this feature.

However, recently I found that this static super reference can be mimicked by super to some extent. This may be the reason of why static super reference not mentioned in this book.

See the next comments below.

In Programming in Scala, the authors says

One difference is especially important: the interpretation of super. With multiple inheritance, the method called by a super call can be determined right where the call appears.

Although this is not the precise definition of “multiple-inheritance”, but it reveals an important feature of it. The static super reference is a bit like this kind of multiple inheritance feature in the book.

“multiple-inheritance with linearization”, I like this term :grinning:. It captures the essence of Scala’s trait system.

Yes, Scala supports “multiple-inheritance”, but it is “multiple-inheritance with linearization”. It is slightly different from the multiple inheritance (“multiple-inheritance with copy”) which I learned before.

More details see the next comments below.

Yes, but how to define “better”?

IMO, if A is better than B, then A can simulate B but B can not simulate A, otherwise they are not comparable.

See the next comments below.

Recently I further studied the Scala’s trait system.

I found that Scala may not fully support multiple inheritance.

The definition of multiple inheritance here is “multiple-inheritance with copy”, that means:

Suppose we have three traits A B and C. B and C are inheriting from A, and a fourth trait D multiple inheriting from B and C. The expected behavior is that D will get two copies of A and if there are conflicting members in B and C, then D will have two different versions of those conflicting members.

Although many people think this kind of multiple-inheritance is rarely desirable (so it is bad), I have a different point.

The key thing is why we want multiple-inheritance?

We want multiple-inheritance is because we want to reuse code.

There are two main use cases of code reuse via inheritance: “partially ordered” vs “symmetric”

Just imagine, suppose we have two traits Window and Door, they are developed independently, now we want to define the 3rd trait called WindowDoor which reuse the code of Window and Door. (PS: I admit this example may be a bit weird, you can think of Window, Door and WindowDoor are some sort of service trait) The problem is what if there is a conflicting member between Window and Door? There are two ways to deal with these conflicting members (1) override (2) alias. They correspond to “partially ordered” and “symmetric” respectively.

In general, we want “symmetric” for states and “partially ordered” for methods. But in some situations, we also want “symmetric” for methods. For example, if the conflicting members in Window and Door are just coincidence, we just alias them.

Scala supports “partially ordered” for methods in natural. It also supports “symmetric” for states and methods, even “partially ordered” for states, but they require a lot of boilerplate code.

For example, following code demonstrates “symmetric” for states and methods in Scala:

trait Window {
  private[this] var _state: Boolean = false
  def state: Boolean = _state
  def state_=(value: Boolean): Unit = {
    _state = value
  }
  def open() = {
    println("Window open")
    _state = true
  }
}

trait Door {
  private[this] var _state: Boolean = false
  def state: Boolean = _state
  def state_=(value: Boolean): Unit = {
    _state = value
  }
  def open() = {
    println("Door open")
    _state = true
  }
}

trait RenamedDoor extends Door {
  override def state: Boolean = throw new Exception("RenamedDoor state excluded state")
  override def state_= (x : Boolean) = throw new Exception("RenamedDoor state_= excluded state")
  def stateDoor: Boolean = super.state
  def stateDoor_=(value: Boolean): Unit = {
    super.state_=(value)
  }
  def DoorOpen() = {
    println("RenamedDoor DoorOpen")
    super.open()
  }
}

trait RenamedWindow extends Window {
  override def state: Boolean = throw new Exception("RenamedWindow state excluded state")
  override def state_= (x : Boolean) = throw new Exception("RenamedWindow state_= excluded state")
  def stateWindow: Boolean = super.state
  def stateWindow_=(value: Boolean): Unit = {
    super.state_=(value)
  }
  def windowOpen() = {
    println("RenamedWindow windowOpen")
    super.open()
  }
}

trait WindowDoor extends RenamedWindow with RenamedDoor {
  override def state: Boolean = throw new Exception("RenamedWindow state excluded state")
  override def state_= (x : Boolean) = throw new Exception("RenamedWindow state_= excluded state")
  override def open() = {
    println("WindowDoor open")
    DoorOpen()
    windowOpen()
  }
}

object WindowDoor {
  def create(s1 : Boolean, s2: Boolean) : WindowDoor = {
    val obj = new WindowDoor {}
    obj.stateWindow = s1
    obj.stateDoor = s2
    obj
  }
}

val windowDoor = WindowDoor.create(false, false)
windowDoor.open()
windowDoor.stateDoor
windowDoor.stateWindow

As you see, we need to define 2 proxy traits (for method redirect) and override the conflicting members (for excluding them). Although a bit cumbersome, Scala support both of them (It would be nice, If there are some trait operators which can automatically do these stuff)

The “partially ordered” for states is rarely desirable, see following code:

trait IStateAndBehavior {
  def state: Boolean
  def state_=(value: Boolean)
  def open() : Unit
}

trait IStateAndBehaviorDefaultImpl extends IStateAndBehavior {
  def open(): Unit = println("IStateAndBehaviorDefaultImpl open")
}

trait Window extends IStateAndBehavior{
  private[this] var _state: Boolean = false
  override def state: Boolean = _state
  override def state_=(value: Boolean): Unit = _state = value
  abstract override def open() = {
    println("Window open")
    super.open()
    _state = true
  }
}

trait Door extends IStateAndBehavior {
  private[this] var _state: Boolean = false
  override def state: Boolean = _state
  override def state_=(value: Boolean): Unit = _state = value
  abstract override def open() = {
    println("Door open")
    super.open()
    _state = true
  }
}

class WindowDoor(s1 : Boolean, s2: Boolean) extends IStateAndBehaviorDefaultImpl with Window with Door {
  super[Window].state_=(s2)
  super[Door].state_=(s1)
}

val windowDoor = (new WindowDoor(false, false))
windowDoor.open()

So we can says that Scala supports all 4 kinds of multiple-inheritance use cases:

“partially ordered” for methods

“partially ordered” for states

“symmetric” for methods

“symmetric” for states

The problem is what if the traits Window and Door inheriting from the trait Thing which has states? (Again, the example is really weird, but you can think of they are some sort of service. The Window service and Door service use the Thing service)

The expected behavior is that the WindowDoor will get two copies of Thing, no matter “partially ordered” for states or “symmetric” for states.

Scala does not support this kind of thing and there is no way to work around it (by inheritance). The reason is linearization. Scala linearization ensures that there will be only one Thing.

Therefore here are some of my conclusions:

  1. Scala use “multiple-inheritance with linearization”. It differs from “multiple-inheritance with copy” and there are some use cases of “multiple-inheritance with copy” can not be implemented in Scala.

  2. Scala’s multiple-inheritance looks like “symmetric” in form, but it is “partially ordered” in essence.

What do you think about it ?

It is what it is. Diamond inheritance is a long-known problem, and different languages have tackled it in different ways – Scala’s approach is traits with linearization, which involves some tradeoffs that you’ve identified.

I should note: I usually regard this level of inheritance complexity as a sign of un-idiomatic code. Scala idiom leans towards “prefer composition over inheritance”, and I will usually reject code long before it gets anywhere near this complex – due to its other features (not least, the FP side of the language), there tend to be more-appropriate designs. (For example, I would usually not conflate state and behavior like this, but would recommend using a typeclass for the behavior side.)

3 Likes

That’s not a universal definition of multiple inheritance. Notably, C++ supports both the copying and non-copying kinds — the latter being introduced with the virtual keyword in the extension clause. It’s still called (virtual) multiple inheritance, and Scala only has that kind of multiple inheritance.

It’s also been accepted, in C++ circles, that the copying kind can be very confusing and is better avoided.