Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to solve the inheritance of conflicting members in Scala

Tags:

scala

I'm a Scala beginner. I was told that "a field in a trait can be concrete or abstract".

trait T2 {
  val f1: String = "T2f1"
}

trait T3 {
  val f1: String = "T3f1"
}

class C2 extends T2 with T3{}

object Test2 extends App {
  val c2 = new C2
  println(c2.f1)
}

When I ran the code above, the compiler printed some error messages:

class C2 inherits conflicting members:
  value f1 in trait T2 of type String  and
  variable f1 in trait T3 of type String
(Note: this can be resolved by declaring an override in class C2.)
class C2 extends T2 with T3{}

So what should be changed if C2 extend traits that have the same field name? Thank you for your help.

like image 642
hawkcraft Avatar asked Oct 31 '25 08:10

hawkcraft


2 Answers

The accepted answer is correct, but bear in mind that the suggested pattern is strange and could lead to hard to understand bugs in non-trivial cases. In my experience, overriding non-abstract vals will only get you in trouble.

The problem is that the initialisation code is part of the constructor of the defined class/trait. This means that the code initialising both T2.f1 and T3.f1 will be executed when creating an instance of C2:

trait T2 {
    val f1: String = {
        println("T2")
        "T2f1"
    }
}

trait T3 {
    val f1: String = {
        println("T3")
        "T3f1"
    }
}

class C2 extends T2 with T3 {
    override val f1: String = {
        println("C2")
        "T3f1"
    }
}

new C2 // Will print "T2", then "T3", then "C2"

If the initialisation code has any important side-effect, this might result in hard-to-trackdown bugs! It also has the disadvantage of forcing you to repeat some of T3's code in C2.

If you don't absolutely need T2.f1 and T3.f1 to be vals, you might be better off using defs to avoid initialisation code in abstract vals:

trait T2 {
    def f1: String = "T2f1"
}

trait T3 {
    def f1: String = "T3f1"
}

class C2 extends T2 with T3 {
    override val f1: String = "C2f1" // You can keep this a def if you like
}

In case you really need f1 to be a val, e.g. if you need a stable value to use it in pattern matching statements, you can use the following:

trait T2 {
    val f1: String
    protected def computeF1: String = {
        println("T2")
        "T2f1"
    }
}

trait T3 {
    val f1: String
    protected def computeF1: String = {
        println("T3")
        "T3f1"
    }
}

class C2 extends T2 with T3 {
    override val f1: String = computeF1 // You can keep this a def if you like
    override protected def computeF1: String = super[T3].computeF1
}

new C2 // Only prints "T3" once

This last solution is a bit more verbose but it bypasses the problem altogether by avoiding the overriding of a non-abstract val.

like image 117
francoisr Avatar answered Nov 03 '25 00:11

francoisr


You can resolve ambiguity manually:

  trait T2 {
    val f1: String = "T2f1"
  }

  trait T3 {
    val f1: String = "T3f1"
  }

  class C2 extends T2 with T3 {
    override val f1: String = "T2f1"
  }

or

  trait T2 {
    val f1: String = "T2f1"
  }

  trait T3 {
    val f1: String = "T3f1"
  }

  class C2 extends T2 with T3 {
    override val f1: String = "T3f1"
  }

Compiler could do this automatically via linearization if it were

  trait T2 {
    val f1: String = "T2f1"
  }

  trait T3 extends T2 {
    override val f1: String = "T3f1"
  }

  class C2 extends T2 with T3

Conflicting fields in Scala Traits

like image 20
Dmytro Mitin Avatar answered Nov 02 '25 23:11

Dmytro Mitin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!