Multiple inheritance - alternative solutions?

So I guess I’ll ask this, Scala 3 is pretty historical so even if I think nothing will come out of this because it’s too late for such a fundamental change and I’m also probably wrong.

I think I heard years ago now an explanation along the lines of “if your class extends a door with a window and you open() it, you’re opening the door”

I never thought about it like that, for me it was always like that class is both a window and a door (the example falls apart here a bit)

This also came up recently when I was experimenting trying to extend an intersection type, the non-commutability of inherited traits given to me as explanation on gitter. So the introduction of intersection types is further motivation for me to bring this up.

Also the way it currently works feels weirdly implicit to me.

Can’t think of any way to have these changes than to force the user to disambiguate everythig, override ambiguous methods (possibly delegating to one of the traits implementation), etc. Not sure how this would play with the new trait parameters.

I’d prefer that… anyway, just wanted to have this out here for discussion before the final feature freeze, thanks for your attention :wink:

“if your class extends a door with a window and you open() it, you’re opening the door”

Are you saying that’s how it should be and therefore Scala gets it wrong?


Welcome to Scala 2.13.0 (OpenJDK 64-Bit Server VM, Java 1.8.0_222).
Type in expressions for evaluation. Or try :help.

> trait Opening { def open(): Unit }
defined trait Opening

> trait Window extends Opening { override def open(): Unit = println("Opening window") }
defined trait Window

> trait Door extends Opening { override def open(): Unit = println("Opening door") }
defined trait Door

> class Doordow extends Door with Window
defined class Doordow

> val myDoordow = new Doordow
myDoordow: Doordow = Doordow@33d60b7e

> myDoordow.open()
Opening window
2 Likes

I’m saying that I’d probably prefer to be forced to disambiguate explicitly (overriding open(), maybe with some helper syntax thrown in, at this point that’s just a detail) and gain commutativity of inherited traits in the process.

I imagine this is not a very popular opinion.

Btw, I’d have expected “Opening door” to be printed out. It makes even less sense to me this way. While lack of knowledge is not an argument in favour of what I’m saying, I think my misunderstanding shows the drawbacks of this implicitness that I percieve here. I’m sure It all works out according to rules in the spec but I don’t think it’s intuitive, and non-intuitive + implicit is not a good combo imo.

edit: I might have read that door/window explanation wrt Ptyhon and not Scala after all… not sure was quite a while ago

Door with Window is not commutative, even in Dotty; Window overrides Door, and in general what’s on the right of with overrides what’s on the left.

3 Likes

“When the Lord closes a door, somewhere he opens a window.” From Sound of Music.

Obviously this wreaks havoc with resource management.

4 Likes

I’m not sure if it’s relevant to the types of things you’re trying to do via multiple inheritance, but it feels like the kind of problem that I would tend to solve using typeclasses and tagging instead of multiple inheritance.

That way you can have one object and you can open either the door or the window, and you’ll have better control over which is going to happen.

1 Like

There is the only one real problem with methods in scala:
if you want to work with interfaces you always need to declare traits. It is always double work.

I think there are no problem with multiple inheritance with methods\interfaces.

The real difficulties is in data inheritance(diamond problem).
Which can be solved with dependency injection or chain-of-responsibility pattern.

Scala implements chain-of-responsibility pattern by traits. It seems that dependency injection can be used with ‘export’.

IMHO: The real difficulties is only double work with “interface” declaration.

Of course someone always can shoot himself in the foot with any tool. But it is not a language guild(Shooting yourself in the foot in various programming languages(joke))

I’m not sure what do you mean, but I would welcome an annotation that makes compiler automatically generate a trait containing all public members (or even have configurable minimum visibility). For example:

@generateInterfaceTrait(name="UsersIfc", minimumVisibility="protected")
class Users(db: Database) {
  def addUser(user: User): Future[Done] = ???
  protected def removeUser(userId: UserId): Future[Done] = ???
  private def doInTransaction[T](action: => T): T = ???
}

generates additional trait:

trait UsersIfc {
  def addUser(user: User): Future[Done]
  protected def removeUser(userId: UserId): Future[Done]
}

and makes Users extend UsersIfc.

Some people are keen on generating useless traits and such annotation would at least shorten that (extra trait) boilerplate to one line.

1 Like

How to shoot yourself in the foot with Scala:


for {

leg <- self.legs

foot <- leg.feet

gun <- self.arsenal.weapons.filter(_.canBeFired)

} yield gun.target(foot).aim.pullTrigger.doItVeryUnsafely match {

case Success(bulletHole) => println(bulletHole.message.long)

case Failure(error) => println(error.getMessage)

}

error: could not find implicit value for parameter trajectory: Path[Projectile[HandGun, Triggered], TargetEvidence[BioPart, Nothing], Any]

6 Likes

I think it partially improve situation at least in some cases.
But It will not solve problem in general because it is not default behavior and they will not do such things by default.
I am unsure that it can be improved for scala in general today. But in some languages there are no differences between classes and interfaces, thay alway work with interfaces.
May be:

@interface // by default(className="UserImpl", minimumVisibility="protected")
class Users(db: Database) {
  def addUser(user: User): Future[Done] = ???
  protected def removeUser(userId: UserId): Future[Done] = ???
  private def doInTransaction[T](action: => T): T = ???
}

which generate pure interface Users with class UserImpl.

So whenever you use “Users”, you use interface.

While “useless” is probably a bit of a stretch (if nothing else, they make writing tests significantly easier), some way to reduce the duplicate work would be really nice.

I think Linearization might be something you are looking for. Did you read the Class Linearization section in Scala Language Specification?