0

I have a super and a subclass as follows:

class Animal(var x: Int) {
   def greeting: String = "hi im an animal"
   def copy: Animal = new Animal(x)
}


class Lion(override var x: Int) extends Animal(x){
  override def greeting: String = "hi im a lion"
  override def copy: Lion = new Lion(x)
}

I want both of them to have the exact same copy function (imagine it being larger than what I've given), except for the return type, I would like the Lion class to return a Lion when copy is invoked.

How can I cleanly override the Animal copy method without having code duplication?

TomatoFarmer
  • 463
  • 3
  • 13
  • 4
    Check [this](https://tpolecat.github.io/2015/04/29/f-bounds.html) TL;DR; this doesn't work using normal inheritance, you either need **F-Bounded** or **Typeclasses**; or rethink your design. – Luis Miguel Mejía Suárez Oct 24 '20 at 14:34
  • This example is simplified so I don't know if it will fit in your design but you can consider writing a constructor for Lion that takes Animal as a parameter. Then the copy method will look like this: `override def copy: Lion = new Lion(super.copy)`. Of course it only has sense if the additional constructor that creates a Lion from Animal is less complicated that writing whole copy method logic again in Lion class. – Ava Oct 25 '20 at 13:54
  • @TurgutKursun Your code doesn't compile with `mutable variable cannot be overridden`. – Dmytro Mitin Oct 27 '20 at 17:56

1 Answers1

0

In principle, methods apply/unapply, canEqual/equals/hashCode, toString, copy, productArity/productElement/productIterator/productPrefix can be generated with Shapeless case classes a la carte although I'm not sure whether this works with class hierarchies.

Anyway, you can generate apply with a macro annotation

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("enable macro annotations")
class copy extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro CopyMacro.impl
}

object CopyMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    annottees match {
      case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
        val paramNamess = paramss.map(_.map {
          case q"$_ val $tname: $_ = $_" => tname
          case q"$_ var $tname: $_ = $_" => tname
        })

        val tparamNames = tparams.map {
          case q"$_ type $tpname[..$_] = $_" => tpname
        }

        val doesOverrideCopy = parents.map {
          case q"${parent@tq"$_[..$_]"}(...$_)" => parent
          case     parent@tq"$_[..$_]"          => parent
        }.exists(tree => 
          c.typecheck(tree.duplicate, mode = c.TYPEmode)
            .tpe
            .member(TermName("copy")) != NoSymbol
        )

        val copyMod = if (doesOverrideCopy) Modifiers(Flag.OVERRIDE) else NoMods

        q"""
           $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
             ..$stats
             $copyMod def copy: $tpname[..$tparamNames] = new $tpname[..$tparamNames](...$paramNamess)
           }
           ..$tail
        """
    }
  }
}

Usage:

@copy
class Animal(val x: Int) {
  def greeting: String = "hi im an animal"
}

@copy
class Lion(override val x: Int) extends Animal(x) {
  override def greeting: String = "hi im a lion"
}

//scalac: {
//  class Animal extends scala.AnyRef {
//    <paramaccessor> val x: Int = _;
//    def <init>(x: Int) = {
//      super.<init>();
//      ()
//    };
//    def greeting: String = "hi im an animal";
//    def copy: Animal = new Animal(x)
//  };
//  ()
//}
//scalac: {
//  class Lion extends Animal(x) {
//    override <paramaccessor> val x: Int = _;
//    def <init>(x: Int) = {
//      super.<init>();
//      ()
//    };
//    override def greeting: String = "hi im a lion";
//    override def copy: Lion = new Lion(x)
//  };
//  ()
//}

Alternatively, since class Animal(val x: Int) is case-class-like you can try to use shapeless.Generic

implicit class CopyOps[A](a: A)(implicit generic: Generic[A]) {
  def copy: A = generic.from(generic.to(a))
}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66