Reputation: 745
I want to have a case class extend a trait
Here are my requirements:
trait ParentParams { var name: String = "Al" }
then the child should not be case class ChildParams(id: String = "whatever", override val name: String = Parent.defaultName) extends Parent
. I would prefer to have it as case class Child(id: String) extends Parent
. However, when I try the second approach, I am not able to access the name field when I try to do a .copy()
on a case class object. As a workaround I can mutate the value of the name
attribute through childObj.name = "newName"
var
instead of a val. However, ideally I would like to make it immutable and use the copy method for changing values instead.This is what I have right now, but am unable to use the .copy()
method to change the value of an inherited attribute.
package abcd.wxyz
import org.scalatest.{FlatSpec, Matchers}
trait ParentTrait {
var name: String = "name"
}
case class TestParams(param1: String = "123") extends ParentTrait
class TestParamsSpec extends FlatSpec with Matchers {
"TestParams" should "be able to access it's inherited attributes and modify them" in {
val testParams = TestParams()
testParams.name = "newName1"
testParams.param1 should equal("123")
testParams.name should equal("newName1")
val modifiedTestParams = testParams.copy(name = "newName2") // cannot resolve symbol name
modifiedTestParams.name should equal("newName2")
}
}
I get a "Cannot resolve symbol name" on .copy(name = "newName2")
for the code above.
The reason behind having this inheritance is to have a function which accepts a Child object and expects it to have a "name" attribute defined. For example, consider a function which will append "Mr." or "Mrs." given a Child object having a name attribute.
Overall background of what is happening:
I am filling a config with command line input values (into a case class) using scopt. If no value is provided, a default is used for that attribute. By attribute I mean the data member inside the case class.
I have a function (let's call it dbReader) which accepts an object of the case class and uses it to establish a connection and read from a database. There are many different parameter case classes in my project and I want each of these parameters to implement a common DatabaseConnectionParameters
trait so that the function dbReader can work regardless of which parameter case class is passed to it.
Upvotes: 1
Views: 2395
Reputation: 40510
case class Child(id: String = "55", override val name: String = super.name) extends Parent
Does not work, but
case class Child(id: String = "55", override val name: String) extends Parent(name)
does ...
You have to provide a constructor parameter to Parent
to be able instantiate it.
Upvotes: 0
Reputation: 20561
In scala, a default value for a constructor is compiled as a function which evaluates in the context of the companion object, so
case class CaseClazz(foo: String = super.bar) extends Bar
Compiles to something like (I'm not mangling the names in the interest of clarity):
class Bar { def bar: String = "whatevs" }
case class CaseClazz(foo: String = CaseClazz.defaultForFoo) extends Bar
object CaseClazz extends Function1[String, CaseClazz] {
def defaultForFoo: String = super.bar
def apply(foo: String = defaultForFoo): CaseClazz = new CaseClazz(foo)
}
Which doesn't compile because super
in the object is CaseClazz$
, which doesn't define a bar
method.
The clearest way to get the behavior you seek is IMO:
object Parent {
val defaultName: String = "Al"
}
class Parent(val name: String = Parent.defaultName) { def parentBehavior: Unit = println("parent!") }
case class Child(id: String = "55", override val name: String = Parent.defaultName) extends Parent(name)
Which allows:
scala> Child(id = "ego").parentBehavior
parent!
scala> Child(id = "ego").name
res12: String = Al
scala> Child().name
res13: String = Al
scala> Child().parentBehavior
parent!
Upvotes: 6