case class copy 'method' with superclass

Tag: class , scala , inheritance , enumeration Author: meizhong Date: 2012-08-26

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.

can you please explain what do you want to obtain by overriding a val in a case class?
well, it won't have a value in the abstract super class. Maybe seeing the actual program would help this question make more sense, in the actual code the "Base" class is the abstract sealed "Piece" class defined in this file: github.com/nairbv/scalachess/blob/master/src/main/scala/… ... as a temporary hack, I threw in the ugly cast I mentioned as a possibility in the original question.
Your comment, "I have like 6 of these simple case classes though, ideally with some kind of copy method I'd save a lot of typing...", sounds like a use case. I'm not sure how the answer you prefer addresses your problem, though I understand the trade-offs of the other variant replies, and I also appreciate the effort to reformulate a long answer. You got to +1 anything that mentions Shapeless, but did you really refactor to HLists?

Best Answer

This is old answer, before the question was changed.

Your approach is just wrong not only in Scala, but in any object oriented programming which correctly respect encapsulation.

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 only as visible through Base class or interface, i.e. the properties and methods defined only on the Base class/interface or its parents. The key point here is that no matter what is the runtime type of a, the compile type is Base , so your method can only treat the a as Base

What you are trying to force here is extremely unsafe, since you want to say:

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.

What if I write this code then?

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")

How do you expect getIt to behave when providing an instance of Evil? Throwing a RuntimeException "MethodNotFound" ?

The power of type safe programming languages lie in the fact that the compiler is able to verify you correctly respect the contract of your types in your code. 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. I will 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. This is in Scala is the well known mechanism of default parameters. When using copy, you specify only the fields you want to change.

Let's go down in our analysis and take again your 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")

Supposing you were able to define on Base a contract such that this would compile, how would you write it? According to what we have said just before, your copy contract should have the following signature

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

You know should have understood that there is no way to express this contract in Scala. Not only, you should have the intuition that there is a strict relation between case classes and Tuples in Scala. In fact case classes are terribly similar to tuples with additional behaviour.

Pushing our reasoning, you understand that your problem is the number of properties of your classes hierarchy is not guaranteed to be the same, i.e. you can have subclasses with more parameters that will have a different copy signature

In practice, supposing AnyTuple[Int] describes any Tuple of any size of Int, you would like a method which does the following:

def copyTupleChangingFirstElement(myParam:AnyTuple[Int], newValue:Int) = myParam.copy(_1=newValue)

While this is logically feasible, because every tuple will contain at least the first element which you might want to change, this is not possible in Scala without very advanced programming. What you would like to do is called

"Abstracting over arity"

i.e. treating tuples of different size in a generic way, independently of their size. This is not simple however, and that is also why you have different classes from Tuple1 to Tuple22 in Scala


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:

  1. 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 = [email protected]
    
    scala>  getIt(res0)
    res1: Foo = [email protected]
    
    scala>  res1.param
    res2: String = hello
    
  2. 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

comments:

hmm... what about parameters that are overriden in Foo and Bar from the parent? When I came up with my example code based on my actual program, it looks like I made a mistake; in my actual program all attributes of Foo and Bar are override's of attributes of Base.
I have answered to your question. If you change your question , I will change my answer :)
haha, OK, I edited the question :-)
What was deprecated was case-class-from-case-class inheritance.
can you post your answer and explain why only case-class-from-case-class inheritance was deprecated?

Other Answer1

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)

comments:

hmm.... I have like 6 of these simple case classes though, ideally with some kind of copy method I'd save a lot of typing instead of having to implement a withMyParam in every case class :-/
Why do you have different case classes which have exactly the same arguments? Not necessarily bad, just trying to figure out.
well, I'm kind of using them like enumerations. I could almost use enumerations instead, but there's the one parameter that can vary.
How about case class MyEnum(etype: Etype, value: String); sealed trait Etype; object etype1 extends Etype; ... ?
Why? Pattern matching can be nested, like case MyEnum(`etype1`, x) => .... Note that you need to use backticks otherwise Scala will treat the lower-case name as a free match variable. You can use upcase objects to prevent shooting in the foot.

Other Answer2

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.

Other Answer3

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)

Other Answer4

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

Other Answer5

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]
  }