Unexpected behavior when inheriting multiple traits

44 views
Skip to first unread message

Amer Sinha

unread,
Jun 21, 2016, 9:51:37 AM6/21/16
to scala-user
Hi

I am not exactly sure if this is a bug or if it is supposed to behave like this but it seems very unintuitive. I have a class A extending 2 different traits, lets call them B and C. Now these traits are extending a trait D which has method superFunc(). superFunc() calls a method getSomething which is declared in D but the implementation only exists in B and C. Now, when I use superFunc() as super[B].superFunc() and super[C].superFunc() in A, they behave exactly the same whereas I would expect them to behave differently. 

  
class A extends B with C {
 
super[B].superFunc()
 
super[C].superFunc()
}

trait D
{
  def getSomething: String
  def superFunc: /* some code */ getSomething() /* some more code */
}

trait B
extends D {
 
def getSomething: String = "hello"
}

trait C
extends D {
 
def getSomething: String = "world"
}
 

Is this the expected behavior?

Thanks
Amer

Oliver Ruebenacker

unread,
Jun 21, 2016, 11:21:46 AM6/21/16
to Amer Sinha, scala-user

     Hello,

  Yes, it is expected. Based on the runtime type of an object, when method implementation I2 of method M overrides implementation I1 of M, then I2 is used for all calls of M, even calls where I1 is in scope and I2 is not.

  For example:

scala> trait A { def f = g; def g = "Hello!" }
defined trait A

scala> class B extends A { override def g = "World" }
defined class B

scala> val b = new B
b: B = B@d771cc9

scala> b.f
res0: String = World
 
     Best, Oliver


--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Oliver Ruebenacker
Senior Software Engineer, Diabetes Portal, Broad Institute

Amer Sinha

unread,
Jun 21, 2016, 9:27:55 PM6/21/16
to Oliver Ruebenacker, scala-user

I understand your example but if you read my question, it is related more to how functions are inherited from multiple traits when there are naming conflicts than just simply overriding a method. Your example is very straightforward and does not capture any of the intricacies of my question.

If I understand you correctly, do you mean when I call super[B].superFunc(), it is actually calling the getSomething function from C because of linearization? That seems like a very unintuitive way to define functionality in a language. As far as I understand it, scala tries to be very functional and this does not seem to be something that is line with that type of thinking.

Meeraj Kunnumpurath

unread,
Jun 22, 2016, 2:28:00 AM6/22/16
to scala-user
Does the code compile?

Welcome to Scala version 2.11.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_51).
Type in expressions to have them evaluated.
Type :help for more information.

scala> trait A {
     | def foo: String
     | def bar = foo
     | }
defined trait A

scala> trait B extends A {
     | def foo = "Hello"
     | }
defined trait B

scala>

scala> trait C extends A {
     | def foo = "World"
     | }
defined trait C

scala> class D extends B with C {
     | super[B].bar
     | super[C].bar
     | }
<console>:10: error: class D inherits conflicting members:
  method foo in trait B of type => String  and
  method foo in trait C of type => String
(Note: this can be resolved by declaring an override in class D.)
       class D extends B with C {
             ^

scala>
Meeraj Kunnumpurath
Director and Executive Principal
Service Symphony Ltd
00 44 7702 693597
00 971 50 409 0169
mee...@servicesymphony.com

Roland Kuhn

unread,
Jun 22, 2016, 2:39:58 AM6/22/16
to Amer Sinha, Oliver Ruebenacker, scala-user
Hi Amer,

in Scala there is only one “slot” for each method signature in an object’s dispatch table—this is the same as declaring every method “virtual” in C++ (for example). This behavior is simpler to specify and use than the behavior you expected, and it is the only possible choice on the JVM. Think about what it would require to implement what you suggest: class A would need to contain two different objects B and C which each have their own implementation of D (in order to be able to lead to different results)—this would be very confusing as soon as trait D contains a variable.

Regards,

Roland

Amer Sinha

unread,
Jun 22, 2016, 5:41:45 PM6/22/16
to scala-user
Let me make it a bit more clear on what is happening and give you code that you can compile to confirm. 

trait D {
  def getSomething: String
  def superFunc: String = getSomething
}

trait B
extends D {
 
override
def getSomething: String = "hello"
}

trait C
extends D {
 
override
def getSomething: String = "world"
}

class A extends B with C {

 
def makeStringUsingSuperFunc: String = super[B].superFunc + super[C].superFunc
 
def makeString: String = super[B].getSomething + super[C].getSomething
}

Now, you can create an object of type A. 

val a = new A

And check that the output for a.makeStringUsingSuperFunc and a.makeString differs. a.makeStringUsingSuperFunc = "worldworld" whereas a.makeString = "helloworld"

I hope that makes this clear.

Amer

Simon Ochsenreither

unread,
Jun 22, 2016, 10:20:36 PM6/22/16
to scala-user
I think this makes sense this way:

"super" basically disables dynamic dispatch and let's you statically select which method to call. This is why super[B].getSomething + super[C].getSomething returns "helloworld".

super[B].superFunc + super[C].superFunc also calls superFunc statically, but as superFunc calls getSomething _dynamically_, the standard rules of dynamic dispatch apply again.
Reply all
Reply to author
Forward
0 new messages