Omkar Neogi
Omkar Neogi

Reputation: 745

case class inheriting another class/trait

I want to have a case class extend a trait

Here are my requirements:

  1. I need to use a case class for the child. This is a hard requirement because of scopt (https://github.com/scopt/scopt)
  2. The parent needs to be a trait. Apologies for not being clear about it earlier
  3. I want the attributes of the parent trait to be accessible to the child, and preferrably not have to specify them again when declaring the child case class. For example, if the parent trait is: 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"
  4. I should (hard requirement) be able to override the attribute values that the child object inherited from the parent. Right now this is possible because I have declared the parent attributes as a 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

Answers (2)

Dima
Dima

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

Levi Ramsey
Levi Ramsey

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

Related Questions