Resolving Ambiguities in Mixin Composition

Dear all,

What are the techniques for resolving ambiguities in mixin composition? Has anyone ever worked out a list of those techniques?

Cheers,
–Hossein

Sorry, what ambiguities? Could you give an example?

Sure. Here you are:

scala> trait T1 {val v = ???}
defined trait T1

scala> trait T2 {val v = ???}
defined trait T2

scala> trait T3 extends T1 with T2
<console>:13: error: trait T3 inherits conflicting members:
  value v in class T1$class of type Nothing  and
  value v in class T2$class of type Nothing
(Note: this can be resolved by declaring an override in trait T3.)
       trait T3 extends T1 with T2
             ^

And, what else can be deemed of as ambiguities in this context?

If you’re talking about Scala’s way of trait linearization look here: https://www.trivento.io/trait-linearization/

Yes. This is one way for ambiguity resolution. What other possibility is around?

The weird part to me is that 2.11 reports the location as the trait implementation class. If the member is a def, or using 2.12, it correctly says trait T.

Sorry Som. I am not sure I understand you. And, I don’t see what I guess I understand is relevant here… :confused:

In case you have defs the way to resolve the ambiguity would be overriding v and selecting the one you want with super[X].

scala> trait T1 { def v = 1 }
defined trait T1

scala> trait T2 { def v = 2 }
defined trait T2

scala> trait T3 extends T1 with T2 { override def v = super[T1].v }
defined trait T3

In case you do have vals, you’re pretty much screwed:

scala> trait T1 { val v = 1 }
defined trait T1

scala> trait T2 { val v = 2 }
defined trait T2

scala> trait T3 extends T1 with T2 { override val v = super[T1].v }
<console>:13: error: super may not be used on value v
       trait T3 extends T1 with T2 { override val v = super[T1].v }
                                                                ^

Hi, I think the way to avoid ambiguity in mixin composition is to not do mixin composition. Mixins throw all their members into one big bag of members that you finally have to deal with. They don’t statically enforce discipline about which members are coming from where. You are forced to rely on your IDE to help understand each inherited member.

So, what’s the alternative to mixin composition which fixes these shortcomings? Value composition:

trait T1 { def v: Int }
trait T2 { def v: Int }

trait T3 {
  def t1: T1
  def t2: T2
}

Now, if you have a t3: T3, you can always unambiguously refer to either t3.t1.v or t3.t2.v.

I see Jasper. Thanks! :slight_smile:

I wonder Yawar whether you meant the above sentence to be read with an emphasis on all members being inherited rather than allowing the programmer to selectively name those they want?

If I understand you correctly, you’re saying there is no way to obtain the latter via trait composition. Well, if that truly is so, then, it’s interesting to me for an off-topic reason. (Can you please point me to the place in the Scala spec where that’s stipulated?) But, the question remains on the list of all available options for resolving the ambiguity caused by trait composition.

Mixins are fine. Mixins have two typical use cases:

(1) Promising a type, adding only abstract methods, so the only
implementation will be in the sub-type

e.g.
trait HasName { def name: String }
trait HasId { def id: Long }
case class Employee(name: String, id: Long) extends HasName with HasId

(2) Targeted providing or overriding of method implementations, relying on
linearization

e.g.

trait Executer {
def execute(job: Job): Unit
def handleMessage(message: String): Unit = ()
}
trait AsyncExecuter extends Executer {
def execute(job: Job): Unit = JobQueue.add(job)
}
trait LoggingExecuter extends Executer {
def handleMessage(message: String): Unit = logger.log(message)
}

class AsyncLoggingExecuter extends AsyncExecuter with LoggingExecuter {

}

Sure, see http://www.scala-lang.org/files/archive/spec/2.12/03-types.html#base-types-and-member-definitions , item 3: ‘The member bindings of a type T are…’, subitem 1: ‘all bindings d such that there exists…’. Notice how it says all bindings.

So, inheriting types really get all the members of their parent types. That’s why you have things like https://github.com/scala/scala/blob/v2.12.3/src/library/scala/collection/immutable/List.scala#L427 (Nil.head throws an exception instead of simply not being available for this type).

Thanks! :slight_smile:

1 Like