Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conflicting nested inherited traits

Tags:

scala

traits

Suppose I have the following code:

trait Trait1 { 
  trait Inner {
    val name = "Inner1"
  }
}

trait Trait2 {
  trait Inner {
    val name = "Inner2"
  }
}

class Foo extends Trait1 with Trait2 {
  // I want Concrete1 to be a Trait1.Inner not a Trait2.Inner
  class Concrete1 extends Inner
  val c = new Concrete1
}

object Obj {
  def main(args: Array[String]): Unit = {
    val foo = new Foo
    println(foo.c.name)
  }
}

When I mix in Trait1 and Trait2, referring to Inner seems to default to the Inner type of whichever trait I mixin second; so when I call Obj's main method it prints Inner2. How can I refer to Trait1.Inner in Foo? All three of the following give compiler errors:

class Concrete1 extends Trait1.Inner
class Concrete1 extends Trait1$Inner
class Concrete1 extends Trait1#Inner
like image 502
ams Avatar asked Jan 15 '10 16:01

ams


4 Answers

Instead of

class Concrete1 extends Inner

Use this

class Concrete1 extends super[Trait1].Inner

That should get you what you want

like image 123
Mushtaq Ahmed Avatar answered Nov 07 '22 09:11

Mushtaq Ahmed


There are two namespaces within a template (template being the body of a class, object, or trait.)

  1. Members: vals, vars, and defs and nested objects
  2. Types: types aliases, nested traits and nested classes

When inheriting from multiple parent templates, conflicts in these namespaces are resolved through class linearization.

You could re-order your inheritance to bring the desired parent Inner into your class, or find an alternative design.

like image 27
retronym Avatar answered Nov 07 '22 09:11

retronym


One option (if you can be invasive to the traits) is to define each Inner trait as a type member that has a non-conflicting name.

trait Trait1 {
  type Inner1 = Inner
  trait Inner {
    val name = "Inner1"
  }
}

trait Trait2 {
  type Inner2 = Inner
  trait Inner {
    val name = "Inner2"
  }
}

class Foo extends Trait1 with Trait2 {
  class Concrete1 extends Inner1
  class Concrete2 extends Inner2
  val c1 = new Concrete1
  val c2 = new Concrete2
}

object App extends Application {
  val foo = new Foo
  println(foo.c1.name) // Inner1
  println(foo.c2.name) // Inner2
}

If you cannot be invasive to the original traits (Trait1 and Trait2), you can extend them to define the type member.

trait Trait1 {
  trait Inner {
    val name = "Inner1"
  }
}
trait Trait2 {
  trait Inner {
    val name = "Inner2"
  }
}

trait Trait1a extends Trait1 {
  type Inner1 = Inner
}
trait Trait2a extends Trait2 {
  type Inner2 = Inner
}

class Foo extends Trait1a with Trait2a {
  class Concrete1 extends Inner1
  class Concrete2 extends Inner2
  val c1 = new Concrete1
  val c2 = new Concrete2
}

Another approach would be to use an intermediate trait to define your first concrete class:

trait Trait1 {
  trait Inner {
    val name = "Inner1"
  }
}
trait Trait2 {
  trait Inner {
    val name = "Inner2"
  }
}

trait FooIntermediate extends Trait1 {
  class Concrete1 extends Inner
}

class Foo extends FooIntermediate with Trait2 {
  class Concrete2 extends Inner
  val c1 = new Concrete1
  val c2 = new Concrete2
}
like image 3
Mitch Blevins Avatar answered Nov 07 '22 11:11

Mitch Blevins


Why not order the traits in the order you expect them to have precedence? The linearization of traits is not arbitrary, but specified.

like image 2
Daniel C. Sobral Avatar answered Nov 07 '22 09:11

Daniel C. Sobral