case class copy 'method' with superclass

后端 未结 7 1988
误落风尘
误落风尘 2020-12-05 07:16

I want to do something like this:

sealed abstract class Base(val myparam:String)

case class Foo(override val mypar         


        
相关标签:
7条回答
  • 2020-12-05 07:42

    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.

    0 讨论(0)
提交回复
热议问题