How to add reusable field to Scala Enumeration? - scala

I would like to extend Scala's implementation of Enumeration with a custom field, say label. That new field should be accessible via the values of that enumeration. Furthermore, that custom field should be part of various implementations of Enumeration.
I am aware of the following questions at Stackoverflow:
How to add a method to Enumeration in Scala?
How do I create an enum in scala that has an extra field
Overriding Scala Enumeration Value
Scala doesn't have enums - what to use instead of an enum
However, none of them solves my issues:
The first issue is that I am able to add a custom field. However, I cannot access that additional field via the Values returned by Enumeration.values. The following code works and prints 2nd enumeration value:
object MyEnum extends Enumeration {
type MyEnum = MyVal
val VALUE_ONE = MyVal()
val VALUE_TWO = MyVal(Some("2nd enumeration value"))
val VALUE_THREE = MyVal(Some("3rd value"))
case class MyVal(label: Option[String] = None) extends Val(nextId)
}
import MyEnum._
println(VALUE_TWO.label.get)
Note that I access the label via one of the values. The following code does not work:
for (value <- MyEnum.values) println(value.label)
The error message is as follows: error: value label is not a member of MyEnum.Value
Obviously, instead of MyEnum.MyVal, MyEnum.Val is used. The latter does not define label, while my custom value would provide field label.
The second issue is that it seems to be possible to introduce a custom Value and Val, respectively, in the context of an Enumeration only. Thus, as far as I know, it is not possible to use such a field across different enums. At least, the following code does not compile:
case class MyVal(label: Option[String] = None) extends Enumeration.Val(nextId)
object MyEnum extends Enumeration {
type MyEnum = MyVal
val VALUE_ONE = MyVal()
val VALUE_TWO = MyVal(Some("2nd enumeration value"))
}
object MySecondEnum extends Enumeration {
type MySecondEnum = MyVal
val VALUE_ONE = MyVal()
val VALUE_TWO = MyVal(Some("2nd enumeration value"))
}
Due to the fact that class Val is protected, case class MyVal cannot access Val -- MyVal is not defined in the context of an enumeration.
Any idea how to solve the above issues?

The first issue is addressed by a recent question, my answer to which got no love.
For that use case, I would write a custom widgets method with the useful type, but my linked answer, which just introduces an implicit conversion, seems pretty handy. I don't know why it's not the canonical solution.
For the second issue, your derived MyVal should just implement a trait.
Sample:
scala> trait Labelled { def label: Option[String] }
defined trait Labelled
scala> object A extends Enumeration { case class AA(label: Option[String]) extends Val with Labelled ; val X = AA(Some("one")) }
defined object A
scala> object B extends Enumeration { case class BB(label: Option[String]) extends Val with Labelled ; val Y = BB(None) }
defined object B
scala> val labels = List(A.X, B.Y)
labels: List[Enumeration#Val with Product with Labelled] = List(X, Y)
scala> labels map (_.label)
res0: List[Option[String]] = List(Some(one), None)

Related

Scala collection whose elements can construct sibling instances using named parameters and default values?

I want to have a collection of objects, each object a companion of a different class, which classes all share a common method defined in a superclass that can be invoked when looping through the collection with a foreach(). I want the constructors of these sibling-classes to have the same named parameters and default parameter values as each other. Finally, I want to minimize repeated code.
Thus far, I am trying to do this with case classes, since--if it worked--it would eliminate all the duplicated code of the companion-objects for each type. The problem is that if I put all these companion objects into a Set, when I take them out again I lose the default parameters and parameter names.
Here is some example code of what I am describing:
trait MyType {
val param: String
def label = param // all instances of all subclasses have this method
}
case class caseOne(override val param: String = "default") extends MyType
case class caseTwo(override val param: String = "default") extends MyType
object Main extends App {
// I can construct instances using the companion objects' `apply()` method:
val works1 = caseOne(param = "I have been explicitly set").label
// I can construct instances that have the default parameter value
val works2 = caseOne().label
// But what I want to do is something like this:
val set = Set(caseOne, caseTwo)
for {
companion <- set
} {
val fail1 = companion() // Fails to compile--not enough arguments
val fail2 = companion(param = "not default") // Fails also as param has lost its name
val succeeds = companion("nameless param") // this works but not what I want
println(fail1.label + fail2.label) // this line is my goal
}
}
Notably if the Set has only one element, then it compiles, suggesting the inferred type of the multi-element Set lacks the parameter name--even though they are the same--and the default values. Also suggesting that if I gave the Set the right type parameter this could work. But what would that type be? Not MyType since that is the type of the companion classes rather that the objects in the Set.
I could define the companion objects explicitly, but that is the repeated code I want to avoid.
How can I loop through my collection, constructing instances of MyType subclasses on each iteration, with constructors that have my desired parameter names and default values? All while minimizing repeated code?
Update: Originally the example code showed caseOne and caseTwo as having different default values for param. That was incorrect; they are now the same.
You're not going to be able to get exactly what you want since you don't really have much control over the auto-generated companion objects. In particular for this to work they would all need to extend a common trait. This is why it fails to compile when the set has more than one companion object; even though they all have a method with the same signature, they don't extend a common trait for the compiler to utilize.
You can use a nested case class and get something very similar though:
trait MyType {
val param: String
def label = param // all instances of all subclasses have this method
}
abstract class MyTypeHelper(default: String) {
case class Case(param: String) extends MyType
def apply(param: String) : Case = Case(param)
def apply(): Case = apply(default)
}
object One extends MyTypeHelper("default one")
object Two extends MyTypeHelper("default two")
object Example {
val works1 = One(param = "I have been explicitly set").label
val works2 = One().label
val set = Set(One, Two)
for {
companion <- set
} {
val a = companion()
val b = companion(param = "not default")
val c = companion("nameless param")
println(a.label + b.label)
}
}
Instead of having a caseOne type, you have One.Case, but it still implements MyType so you shouldn't have any issue anywhere else in the code that uses that trait.

Matching against subclasses in macro

I need to convert a string value into an actual type, so i decided to try a macro-way to do this. I have a bunch of data types:
sealed abstract class Tag(val name: String)
case object Case1 extends Tag("case1")
case object Case2 extends Tag("case2")
case object Case3 extends Tag("case3")
etc...
I want to write a simple resolver:
val tag: Tag = TagResolver.fromString("case2")
This line should return Case2 respectively. I manager to do the following:
def typeFromString(c: Context)(name: c.Expr[String]): c.Expr[Tag] = {
import c.universe._
val tag = typeTag[Tag]
val accSymb = tag.tpe.typeSymbol.asClass
val subclasses = accSymb.knownDirectSubclasses // all my cases
subclasses.map { sub =>
val name = sub.typeSignature.member(newTermName("name")).asMethod // name field
???
}
}
But how can i match name: c.Expr[String] against value of name field and if matched return the appropriate tag?
I don't think there's reliable way of doing this, because knownDirectSubclasses can refer to classes that haven't been compiled yet, so we can't evaluate them.
If you can put these values as annotations on the classes, then these annotations can be read even when classes are being compiled in the current compilation run (via the Symbol.annotations API). Please note, however, that knownDirectSubclasses has known issues: https://issues.scala-lang.org/browse/SI-7046.

Custom Scala enum, most elegant version searched

For a project of mine I have implemented a Enum based upon
trait Enum[A] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency
case object GBP extends Currency
}
from Case objects vs Enumerations in Scala. I worked quite nice, till I run into the following problem. Case objects seem to be lazy and if I use Currency.value I might actually get an empty List. It would have been possible to make a call against all Enum Values on startup so that the value list would be populated, but that would be kind of defeating the point.
So I ventured into the dark and unknown places of scala reflection and came up with this solution, based upon the following SO answers. Can I get a compile-time list of all of the case objects which derive from a sealed parent in Scala?
and How can I get the actual object referred to by Scala 2.10 reflection?
import scala.reflect.runtime.universe._
abstract class Enum[A: TypeTag] {
trait Value
private def sealedDescendants: Option[Set[Symbol]] = {
val symbol = typeOf[A].typeSymbol
val internal = symbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol]
if (internal.isSealed)
Some(internal.sealedDescendants.map(_.asInstanceOf[Symbol]) - symbol)
else None
}
def values = (sealedDescendants getOrElse Set.empty).map(
symbol => symbol.owner.typeSignature.member(symbol.name.toTermName)).map(
module => reflect.runtime.currentMirror.reflectModule(module.asModule).instance).map(
obj => obj.asInstanceOf[A]
)
}
The amazing part of this is that it actually works, but it is ugly as hell and I would be interested if it would be possible to make this simpler and more elegant and to get rid of the asInstanceOf calls.
Here is a simple macro based implementation:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
abstract class Enum[E] {
def values: Seq[E] = macro Enum.caseObjectsSeqImpl[E]
}
object Enum {
def caseObjectsSeqImpl[A: c.WeakTypeTag](c: blackbox.Context) = {
import c.universe._
val typeSymbol = weakTypeOf[A].typeSymbol.asClass
require(typeSymbol.isSealed)
val subclasses = typeSymbol.knownDirectSubclasses
.filter(_.asClass.isCaseClass)
.map(s => Ident(s.companion))
.toList
val seqTSymbol = weakTypeOf[Seq[A]].typeSymbol.companion
c.Expr(Apply(Ident(seqTSymbol), subclasses))
}
}
With this you could then write:
sealed trait Currency
object Currency extends Enum[Currency] {
case object USD extends Currency
case object EUR extends Currency
}
so then
Currency.values == Seq(Currency.USD, Currency.EUR)
Since it's a macro, the Seq(Currency.USD, Currency.EUR) is generated at compile time, rather than runtime. Note, though, that since it's a macro, the definition of the class Enum must be in a separate project from where it is used (i.e. the concrete subclasses of Enum like Currency). This is a relatively simple implementation; you could do more complicated things like traverse multilevel class hierarchies to find more case objects at the cost of greater complexity, but hopefully this will get you started.
A late answer, but anyways...
As wallnuss said, knownDirectSubclasses is unreliable as of writing and has been for quite some time.
I created a small lib called Enumeratum (https://github.com/lloydmeta/enumeratum) that allows you to use case objects as enums in a similar way, but doesn't use knownDirectSubclasses and instead looks at the body that encloses the method call to find subclasses. It has proved to be reliable thus far.
The article "“You don’t need a macro” Except when you do" by Max Afonov
maxaf describes a nice way to use macro for defining enums.
The end-result of that implementation is visible in github.com/maxaf/numerato
Simply create a plain class, annotate it with #enum, and use the familiar val ... = Value declaration to define a few enum values.
The #enum annotation invokes a macro, which will:
Replace your Status class with a sealed Status class suitable for acting as a base type for enum values. Specifically, it'll grow a (val index: Int, val name: String) constructor. These parameters will be supplied by the macro, so you don't have to worry about it.
Generate a Status companion object, which will contain most of the pieces that now make Status an enumeration. This includes a values: List[Status], plus lookup methods.
Give the above Status enum, here's what the generated code looks like:
scala> #enum(debug = true) class Status {
| val Enabled, Disabled = Value
| }
{
sealed abstract class Status(val index: Int, val name: String)(implicit sealant: Status.Sealant);
object Status {
#scala.annotation.implicitNotFound(msg = "Enum types annotated with ".+("#enum can not be extended directly. To add another value to the enum, ").+("please adjust your `def ... = Value` declaration.")) sealed abstract protected class Sealant;
implicit protected object Sealant extends Sealant;
case object Enabled extends Status(0, "Enabled") with scala.Product with scala.Serializable;
case object Disabled extends Status(1, "Disabled") with scala.Product with scala.Serializable;
val values: List[Status] = List(Enabled, Disabled);
val fromIndex: _root_.scala.Function1[Int, Status] = Map(Enabled.index.->(Enabled), Disabled.index.->(Disabled));
val fromName: _root_.scala.Function1[String, Status] = Map(Enabled.name.->(Enabled), Disabled.name.->(Disabled));
def switch[A](pf: PartialFunction[Status, A]): _root_.scala.Function1[Status, A] = macro numerato.SwitchMacros.switch_impl[Status, A]
};
()
}
defined class Status
defined object Status

Scala - new vs object extends

What is the difference between defining an object using the new operator vs defining a standalone object by extending the class?
More specifically, given the type class GenericType { ... }, what is the difference between val a = new GenericType and object a extends GenericType?
As a practical matter, object declarations are initialized with the same mechanism as new in the bytecode. However, there are quite a few differences:
object as singletons -- each belongs to a class of which only one instance exists;
object is lazily initialized -- they'll only be created/initialized when first referred to;
an object and a class (or trait) of the same name are companions;
methods defined on object generate static forwarders on the companion class;
members of the object can access private members of the companion class;
when searching for implicits, companion objects of relevant* classes or traits are looked into.
These are just some of the differences that I can think of right of the bat. There are probably others.
* What are the "relevant" classes or traits is a longer story -- look up questions on Stack Overflow that explain it if you are interested. Look at the wiki for the scala tag if you have trouble finding them.
object definition (whether it extends something or not) means singleton object creation.
scala> class GenericType
defined class GenericType
scala> val a = new GenericType
a: GenericType = GenericType#2d581156
scala> val a = new GenericType
a: GenericType = GenericType#71e7c512
scala> object genericObject extends GenericType
defined module genericObject
scala> val a = genericObject
a: genericObject.type = genericObject$#5549fe36
scala> val a = genericObject
a: genericObject.type = genericObject$#5549fe36
While object declarations have a different semantic than a new expression, a local object declaration is for all intents and purpose the same thing as a lazy val of the same name. Consider:
class Foo( name: String ) {
println(name+".new")
def doSomething( arg: Int ) {
println(name+".doSomething("+arg+")")
}
}
def bar( x: => Foo ) {
x.doSomething(1)
x.doSomething(2)
}
def test1() {
lazy val a = new Foo("a")
bar( a )
}
def test2() {
object b extends Foo("b")
bar( b )
}
test1 defines a as a lazy val initialized with a new instance of Foo, while test2 defines b as an object extending Foo.
In essence, both lazily create a new instance of Foo and give it a name (a/b).
You can try it in the REPL and verify that they both behave the same:
scala> test1()
a.new
a.doSomething(1)
a.doSomething(2)
scala> test2()
b.new
b.doSomething(1)
b.doSomething(2)
So despite the semantic differences between object and a lazy val (in particular the special treatment of object's by the language, as outlined by Daniel C. Sobral),
a lazy val can always be substituted with a corresponding object (not that it's a very good practice), and the same goes for a lazy val/object that is a member of a class/trait.
The main practical difference I can think of will be that the object has a more specific static type: b is of type b.type (which extends Foo) while a has exactly the type Foo.

case class copy 'method' with superclass

I want to do something like this:
sealed abstract class Base(val myparam:String)
case class Foo(override val myparam:String) extends Base(myparam)
case class Bar(override val myparam:String) extends Base(myparam)
def getIt( a:Base ) = a.copy(myparam="changed")
I can't, because in the context of getIt, I haven't told the compiler that every Base has a 'copy' method, but copy isn't really a method either so I don't think there's a trait or abstract method I can put in Base to make this work properly. Or, is there?
If I try to define Base as abstract class Base{ def copy(myparam:String):Base }, then case class Foo(myparam:String) extends Base results in class Foo needs to be abstract, since method copy in class Base of type (myparam: String)Base is not defined
Is there some other way to tell the compiler that all Base classes will be case classes in their implementation? Some trait that means "has the properties of a case class"?
I could make Base be a case class, but then I get compiler warnings saying that inheritance from case classes is deprecated?
I know I can also:
def getIt(f:Base)={
(f.getClass.getConstructors.head).newInstance("yeah").asInstanceOf[Base]
}
but... that seems very ugly.
Thoughts? Is my whole approach just "wrong" ?
UPDATE I changed the base class to contain the attribute, and made the case classes use the "override" keyword. This better reflects the actual problem and makes the problem more realistic in consideration of Edmondo1984's response.
This is old answer, before the question was changed.
Strongly typed programming languages prevent what you are trying to do. Let's see why.
The idea of a method with the following signature:
def getIt( a:Base ) : Unit
Is that the body of the method will be able to access a properties visible through Base class or interface, i.e. the properties and methods defined only on the Base class/interface or its parents. During code execution, each specific instance passed to the getIt method might have a different subclass but the compile type of a will always be Base
One can reason in this way:
Ok I have a class Base, I inherit it in two case classes and I add a
property with the same name, and then I try to access the property on
the instance of Base.
A simple example shows why this is unsafe:
sealed abstract class Base
case class Foo(myparam:String) extends Base
case class Bar(myparam:String) extends Base
case class Evil(myEvilParam:String) extends Base
def getIt( a:Base ) = a.copy(myparam="changed")
In the following case, if the compiler didn't throw an error at compile time, it means the code would try to access a property that does not exist at runtime. This is not possible in strictly typed programming languages: you have traded restrictions on the code you can write for a much stronger verification of your code by the compiler, knowing that this reduces dramatically the number of bugs your code can contain
This is the new answer. It is a little long because few points are needed before getting to the conclusion
Unluckily, you can't rely on the mechanism of case classes copy to implement what you propose. The way the copy method works is simply a copy constructor which you can implement yourself in a non-case class. Let's create a case class and disassemble it in the REPL:
scala> case class MyClass(name:String, surname:String, myJob:String)
defined class MyClass
scala> :javap MyClass
Compiled from "<console>"
public class MyClass extends java.lang.Object implements scala.ScalaObject,scala.Product,scala.Serializable{
public scala.collection.Iterator productIterator();
public scala.collection.Iterator productElements();
public java.lang.String name();
public java.lang.String surname();
public java.lang.String myJob();
public MyClass copy(java.lang.String, java.lang.String, java.lang.String);
public java.lang.String copy$default$3();
public java.lang.String copy$default$2();
public java.lang.String copy$default$1();
public int hashCode();
public java.lang.String toString();
public boolean equals(java.lang.Object);
public java.lang.String productPrefix();
public int productArity();
public java.lang.Object productElement(int);
public boolean canEqual(java.lang.Object);
public MyClass(java.lang.String, java.lang.String, java.lang.String);
}
In Scala, the copy method takes three parameter and can eventually use the one from the current instance for the one you haven't specified ( the Scala language provides among its features default values for parameters in method calls)
Let's go down in our analysis and take again the code as updated:
sealed abstract class Base(val myparam:String)
case class Foo(override val myparam:String) extends Base(myparam)
case class Bar(override val myparam:String) extends Base(myparam)
def getIt( a:Base ) = a.copy(myparam="changed")
Now in order to make this compile, we would need to use in the signature of getIt(a:MyType) a MyType that respect the following contract:
Anything that has a parameter myparam and maybe other parameters which
have default value
All these methods would be suitable:
def copy(myParam:String) = null
def copy(myParam:String, myParam2:String="hello") = null
def copy(myParam:String,myParam2:Option[Option[Option[Double]]]=None) = null
There is no way to express this contract in Scala, however there are advanced techniques that can be helpful.
The first observation that we can do is that there is a strict relation between case classes and tuples in Scala. In fact case classes are somehow tuples with additional behaviour and named properties.
The second observation is that, since the number of properties of your classes hierarchy is not guaranteed to be the same, the copy method signature is not guaranteed to be the same.
In practice, supposing AnyTuple[Int] describes any Tuple of any size where the first value is of type Int, we are looking to do something like that:
def copyTupleChangingFirstElement(myParam:AnyTuple[Int], newValue:Int) = myParam.copy(_1=newValue)
This would not be to difficult if all the elements were Int. A tuple with all element of the same type is a List, and we know how to replace the first element of a List. We would need to convert any TupleX to List, replace the first element, and convert the List back to TupleX. Yes we will need to write all the converters for all the values that X might assume. Annoying but not difficult.
In our case though, not all the elements are Int. We want to treat Tuple where the elements are of different type as if they were all the same if the first element is an Int. This is called
"Abstracting over arity"
i.e. treating tuples of different size in a generic way, independently of their size. To do it, we need to convert them into a special list which supports heterogenous types, named HList
Conclusion
Case classes inheritance is deprecated for very good reason, as you can find out from multiple posts in the mailing list: http://www.scala-lang.org/node/3289
You have two strategies to deal with your problem:
If you have a limited number of fields you require to change, use an approach such as the one suggested by #Ron, which is having a copy method. If you want to do it without losing type information, I would go for generifying the base class
sealed abstract class Base[T](val param:String){
def copy(param:String):T
}
class Foo(param:String) extends Base[Foo](param){
def copy(param: String) = new Foo(param)
}
def getIt[T](a:Base[T]) : T = a.copy("hello")
scala> new Foo("Pippo")
res0: Foo = Foo#4ab8fba5
scala> getIt(res0)
res1: Foo = Foo#5b927504
scala> res1.param
res2: String = hello
If you really want to abstract over arity, a solution is to use a library developed by Miles Sabin called Shapeless. There is a question here which has been asked after a discussion : Are HLists nothing more than a convoluted way of writing tuples? but I tell you this is going to give you some headache
If the two case classes would diverge over time so that they have different fields, then the shared copy approach would cease to work.
It is better to define an abstract def withMyParam(newParam: X): Base. Even better, you can introduce an abstract type to retain the case class type upon return:
scala> trait T {
| type Sub <: T
| def myParam: String
| def withMyParam(newParam: String): Sub
| }
defined trait T
scala> case class Foo(myParam: String) extends T {
| type Sub = Foo
| override def withMyParam(newParam: String) = this.copy(myParam = newParam)
| }
defined class Foo
scala>
scala> case class Bar(myParam: String) extends T {
| type Sub = Bar
| override def withMyParam(newParam: String) = this.copy(myParam = newParam)
| }
defined class Bar
scala> Bar("hello").withMyParam("dolly")
res0: Bar = Bar(dolly)
TL;DR: I managed to declare the copy method on Base while still letting the compiler auto generate its implementations in the derived case classes. This involves a little trick (and actually I'd myself just redesign the type hierarchy) but at least it goes to show that you can indeed make it work without writing boiler plate code in any of the derived case classes.
First, and as already mentioned by ron and Edmondo1984, you'll get into troubles if your case classes have different fields.
I'll strictly stick to your example though, and assume that all your case classes have the same fields (looking at your github link, this seems to be the case of your actual code too).
Given that all your case classes have the same fields, the auto-generated copy methods will have the same signature which is a good start. It seems reasonable then to just add the common definition in Base, as you did:
abstract class Base{ def copy(myparam: String):Base }
The problem is now that scala won't generate the copy methods, because there is already one in the base class.
It turns out that there is another way to statically ensure that Base has the right copy method, and it is through structural typing and self-type annotation:
type Copyable = { def copy(myParam: String): Base }
sealed abstract class Base(val myParam: String) { this : Copyable => }
And unlike in our earlier attempt, this will not prevent scala to auto-generate the copy methods.
There is one last problem: the self-type annotation makes sure that sub-classes of Base have a copy method, but it does not make it publicly availabe on Base:
val foo: Base = Foo("hello")
foo.copy()
scala> error: value copy is not a member of Base
To work around this we can add an implicit conversion from Base to Copyable. A simple cast will do, as a Base is guaranteed to be a Copyable:
implicit def toCopyable( base: Base ): Base with Copyable = base.asInstanceOf[Base with Copyable]
Wrapping up, this gives us:
object Base {
type Copyable = { def copy(myParam: String): Base }
implicit def toCopyable( base: Base ): Base with Copyable = base.asInstanceOf[Base with Copyable]
}
sealed abstract class Base(val myParam: String) { this : Base. Copyable => }
case class Foo(override val myParam: String) extends Base( myParam )
case class Bar(override val myParam: String) extends Base( myParam )
def getIt( a:Base ) = a.copy(myParam="changed")
Bonus effect: if we try to define a case class with a different signature, we get a compile error:
case class Baz(override val myParam: String, truc: Int) extends Base( myParam )
scala> error: illegal inheritance; self-type Baz does not conform to Base's selftype Base with Base.Copyable
To finish, one warning: you should probably just revise your design to avoid having to resort to the above trick.
In your case, ron's suggestion to use a single case class with an additional etype field seems more than reasonable.
I think this is what extension methods are for. Take your pick of implementation strategies for the copy method itself.
I like here that the problem is solved in one place.
It's interesting to ask why there is no trait for caseness: it wouldn't say much about how to invoke copy, except that it can always be invoked without args, copy().
sealed trait Base { def p1: String }
case class Foo(val p1: String) extends Base
case class Bar(val p1: String, p2: String) extends Base
case class Rab(val p2: String, p1: String) extends Base
case class Baz(val p1: String)(val p3: String = p1.reverse) extends Base
object CopyCase extends App {
implicit class Copy(val b: Base) extends AnyVal {
def copy(p1: String): Base = b match {
case foo: Foo => foo.copy(p1 = p1)
case bar: Bar => bar.copy(p1 = p1)
case rab: Rab => rab.copy(p1 = p1)
case baz: Baz => baz.copy(p1 = p1)(p1.reverse)
}
//def copy(p1: String): Base = reflect invoke
//def copy(p1: String): Base = macro xcopy
}
val f = Foo("param1")
val g = f.copy(p1="param2") // normal
val h: Base = Bar("A", "B")
val j = h.copy("basic") // enhanced
println(List(f,g,h,j) mkString ", ")
val bs = List(Foo("param1"), Bar("A","B"), Rab("A","B"), Baz("param3")())
val vs = bs map (b => b copy (p1 = b.p1 * 2))
println(vs)
}
Just for fun, reflective copy:
// finger exercise in the api
def copy(p1: String): Base = {
import scala.reflect.runtime.{ currentMirror => cm }
import scala.reflect.runtime.universe._
val im = cm.reflect(b)
val ts = im.symbol.typeSignature
val copySym = ts.member(newTermName("copy")).asMethod
def element(p: Symbol): Any = (im reflectMethod ts.member(p.name).asMethod)()
val args = for (ps <- copySym.params; p <- ps) yield {
if (p.name.toString == "p1") p1 else element(p)
}
(im reflectMethod copySym)(args: _*).asInstanceOf[Base]
}
This works fine for me:
sealed abstract class Base { def copy(myparam: String): Base }
case class Foo(myparam:String) extends Base {
override def copy(x: String = myparam) = Foo(x)
}
def copyBase(x: Base) = x.copy("changed")
copyBase(Foo("abc")) //Foo(changed)
There is a very comprehensive explanation of how to do this using shapeless at http://www.cakesolutions.net/teamblogs/copying-sealed-trait-instances-a-journey-through-generic-programming-and-shapeless ; in case the link breaks, the approach uses the copySyntax utilities from shapeless, which should be sufficient to find more details.
Its an old problem, with an old solution,
https://code.google.com/p/scala-scales/wiki/VirtualConstructorPreSIP
made before the case class copy method existed.
So in reference to this problem each case class MUST be a leaf node anyway, so define the copy and a MyType / thisType plus the newThis function and you are set, each case class fixes the type. If you want to widen the tree/newThis function and use default parameters you'll have to change the name.
as an aside - I've been waiting for compiler plugin magic to improve before implementing this but type macros may be the magic juice. Search in the lists for Kevin's AutoProxy for a more detailed explanation of why my code never went anywhere