So, It is not a global problem. But... its like a DRY problem. Lets introduce it. If I have a trait with some abstract member
trait WithSomeValue {
val someValue: Int
}
and some structure with case classes and their companions both inherited from this trait, if I have to make some static thing like a value and save it in Static(companion), like this:
object Foo extends WithSomeValue {
override val someValue: Int = 1
}
I need to assign it while implement trait in case class too
case class Foo(val x: Int) extends WithSomeValue {
override val someValue: Int = Foo.somevalue// I don`t like to write this! I feel myself DRY and tired ;)
}
What can I do? Can I do something elegant not to write this assignment again and again? To write it in one place only?
I need to have available static members in static context and I have to get an hierarchy - trait->case class both? but I don`t want to write DRY code. I hope some trick exists, to avoid code duplication.
For example you can override the member in a common parent of the case class and its companion object
trait WithSomeValue {
val someValue: Int
}
trait WithSomeValueImpl extends WithSomeValue {
override val someValue: Int = 1
}
object Foo extends WithSomeValueImpl
case class Foo(x: Int) extends WithSomeValueImpl
Foo.someValue // 1
Foo(42).someValue // 1
I don't need one static value for all hierarchy but one value per object companion
What about
trait WithSomeValue {
val someValue: Int
}
trait FooLike extends WithSomeValue {
override val someValue: Int = 1
}
object Foo extends FooLike
case class Foo(x: Int) extends FooLike
trait BarLike extends WithSomeValue {
override val someValue: Int = 2
}
object Bar extends BarLike
case class Bar(x: Int) extends BarLike
?
Actually, this is not a code duplication because a case class and its companion object are absolutely different classes
Class companion object vs. case class itself
someValue
can be implemented in them absolutely differently.
Anyway you can use the following macro annotation (see sbt settings below). It generates the member in the companion object copying the member from a class. If the companion object doesn't exist the annotation creates it.
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
@compileTimeOnly("enable macro annotations")
class companionSomeValue extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro CompanionSomeValueMacros.macroTransformImpl
}
object CompanionSomeValueMacros {
def macroTransformImpl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
def modify(cls: ClassDef, obj: ModuleDef): Tree = (cls, obj) match {
case (
q"$_ class $_[..$_] $_(...$_) extends { ..$_ } with ..$_ { $_ => ..$stats }",
q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }"
) =>
val someValue = stats.collectFirst {
case t@q"$_ val someValue: $_ = $_" => t
}.get
q"""
$cls
$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
..$body
$someValue
}
"""
}
annottees match {
case (cls: ClassDef) :: (obj: ModuleDef) :: Nil => modify(cls, obj)
case (cls: ClassDef) :: Nil => modify(cls, q"object ${cls.name.toTermName} extends WithSomeValue")
}
}
}
// in a different subproject
trait WithSomeValue {
val someValue: Int
}
@companionSomeValue
case class Foo(x: Int) extends WithSomeValue {
override val someValue: Int = 1
}
object Foo extends WithSomeValue
@companionSomeValue
case class Bar(x: Int) extends WithSomeValue {
override val someValue: Int = 2
}
Foo.someValue // 1
Foo(42).someValue // 1
Bar.someValue // 2
Bar(42).someValue // 2
//scalac: {
// case class Foo extends WithSomeValue with scala.Product with scala.Serializable {
// <caseaccessor> <paramaccessor> val x: Int = _;
// def <init>(x: Int) = {
// super.<init>();
// ()
// };
// override val someValue: Int = 1
// };
// object Foo extends WithSomeValue {
// def <init>() = {
// super.<init>();
// ()
// };
// override val someValue: Int = 1
// };
// ()
//}
//scalac: {
// case class Bar extends WithSomeValue with scala.Product with scala.Serializable {
// <caseaccessor> <paramaccessor> val x: Int = _;
// def <init>(x: Int) = {
// super.<init>();
// ()
// };
// override val someValue: Int = 2
// };
// object Bar extends WithSomeValue {
// def <init>() = {
// super.<init>();
// ()
// };
// override val someValue: Int = 2
// };
// ()
//}
Slightly different implementation. The annotation generates the member in a class delegating to the member of the companion object.
@compileTimeOnly("enable macro annotations")
class someValueFromCompanion extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro SomeValueFromCompanionMacros.macroTransformImpl
}
object SomeValueFromCompanionMacros {
def macroTransformImpl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
def modifyClass(cls: ClassDef): ClassDef = cls match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
q"""
$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
..$stats
override val someValue = ${tpname.toTermName}.someValue
}
"""
}
def modify(cls: ClassDef, obj: ModuleDef): Tree = q"..${Seq(modifyClass(cls), obj)}"
annottees match {
case (cls: ClassDef) :: (obj: ModuleDef) :: Nil => modify(cls, obj)
}
}
}
@someValueFromCompanion
case class Foo(x: Int) extends WithSomeValue
object Foo extends WithSomeValue {
override val someValue: Int = 1
}
@someValueFromCompanion
case class Bar(x: Int) extends WithSomeValue
object Bar extends WithSomeValue {
override val someValue: Int = 2
}
//scalac: {
// case class Foo extends WithSomeValue with scala.Product with scala.Serializable {
// <caseaccessor> <paramaccessor> val x: Int = _;
// def <init>(x: Int) = {
// super.<init>();
// ()
// };
// override val someValue = Foo.someValue
// };
// object Foo extends WithSomeValue {
// def <init>() = {
// super.<init>();
// ()
// };
// override val someValue: Int = 1
// };
// ()
//}
//scalac: {
// case class Bar extends WithSomeValue with scala.Product with scala.Serializable {
// <caseaccessor> <paramaccessor> val x: Int = _;
// def <init>(x: Int) = {
// super.<init>();
// ()
// };
// override val someValue = Bar.someValue
// };
// object Bar extends WithSomeValue {
// def <init>() = {
// super.<init>();
// ()
// };
// override val someValue: Int = 2
// };
// ()
//}
Auto-Generate Companion Object for Case Class in Scala (sbt settings for macro annotations)
automatically generate case object for case class
Generate companion object for case class with methods (field = method)