Vishal Shukla
Vishal Shukla

Reputation: 2998

Scala case class inheritance with code reusability

I am trying to model my class hierarchy in case classes. I appreciate related discussion about duplication of case class properties here.

Consider the class hierarchy shown below.

trait Super {
  def a:String
}

case class Child1(a:String, b:String) extends Super {
  override def toString = s" a = $a, b= $b"

}

case class Child2(a:String, c:String) extends Super {
  override def toString = s" a = $a, c= $c"
}

I have a scenario where I want to construct case class objects using basic properties like a, b, c as well as using XML. I created companion objects for these case classes as below.

object Child1 {    
   def apply(node: scala.xml.Node): Child1 = {
      val a = (node \ "a").text
      val b = (node \ "b").text
      Child1(a, b)
  }
}


object Child2 {
    def apply(node: scala.xml.Node): Child2 = {
      val a = (node \ "a").text
      val c = (node \ "c").text
      Child2(a, c)
  }
}

In above code, I have to duplicate the line that parses value of a - (node \ "a").text. There doesn't seem to be a way to do the same even if I convert Super to an abstract superclass.

I wonder how one can do this, which I could have done very easily using abstract class and couple of constructors in Super class in Java.

UPDATE: Qualified name for scala.xml.Node type.

Upvotes: 1

Views: 588

Answers (3)

Odomontois
Odomontois

Reputation: 16308

This is a trivial task for scala.macros or shapeless

Lets define generic xml extractor

import scala.xml.Node

trait Extract[L] extends (scala.xml.Node => L)

And it's simple implementation for string-only containing HList records:

import shapeless._
import shapeless.labelled._

implicit object extractHNil extends Extract[HNil] {
  def apply(node: Node): HNil = HNil
}

implicit def extractHCons[K <: Symbol, L <: HList]
(implicit witness: Witness.Aux[K], recur: Extract[L]) =
  new Extract[FieldType[K, String] :: L] {
    def apply(node: Node): ::[FieldType[K, String], L] = {
      val name = witness.value.name
      val value = (node \ name).text
      field[K](value) :: recur(node)
    }
  }

Now you can construct case class extractor build on top of LabelledGeneric:

implicit def extractCase[C, L]
(implicit lgen: LabelledGeneric.Aux[C, L], extract: Extract[L]) =
  new Extract[C] {
    def apply(node: Node): C = lgen.from(extract(node))
  }

From this point you can add simple mixin for your companions:

abstract trait XmlReader[C] {
  def extract: Extract[C]
  def apply(node: scala.xml.Node) = extract(node)
}

And implement your builders as

object Child1 extends XmlReader[Child1] {
  val extract: Extract[Child1] = implicitly
}

object Child2 extends XmlReader[Child2] {
  val extract: Extract[Child2] = implicitly
}

Now you can verify it:

val node = <node>
  <a>1</a>
  <b>2</b>
  <c>3</c>
</node>

println(Child1(node)) // a = 1, b= 2
println(Child2(node)) // a = 1, c= 3

Note that it's not very hard task to extend such parsers for work with nearly anything defined via sealed families of case classes. See picopickle as example of generic parser built with shapeless

Upvotes: 1

余杰水
余杰水

Reputation: 1384

define a method superA do it

import scala.reflect.runtime.universe._

trait Super {
  def a: String
}

trait Node {
  def \(s: String): String = s
}

object Node {
  // also you can move it to Super companion object 
  implicit class SuperMethodA(val node: Node) {
    def superA = node \ "a"
  }

}

case class Child1(a: String, b: String) extends Super {
  override def toString = s" a = $a, b= $b"

}
object Child1{
  def apply(node: Node): Child1 = {
    val a = node.superA //.text
    val b = (node \ "b") //.text
    Child1(a, b)
  }
}

case class Child2(a: String, c: String) extends Super {
  override def toString = s" a = $a, c= $c"


}
object Child2{
  def apply(node: Node): Child2 = {
    val a = node.superA //.text
    val c = (node \ "c") //.text
    Child2(a, c)
  }
}

Upvotes: 0

lex82
lex82

Reputation: 11307

Indeed this is not possible with a case class. You will have to define a helper to get rid of the duplication like this.

Child1(Super.getAValue(node), c)

Upvotes: 1

Related Questions