Reputation: 2998
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
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
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