How to define a type for a generic type

162 Views Asked by At

Say I have the following code:

trait Trait[T <: Trait[T]] {
  def merge(t: T): T
}

case class A[T <: Trait[T]](t: T, i: Int)
case class B[T <: Trait[T]](t: T, str: String)

Is there a way I can define a type to abbreviate my definitions of classes A and B?

So something like:

type T2 = _ <: Trait[T2] // ???
case class A[T2](t: T2, i: Int)
case class B[T2](t: T2, str: String)
1

There are 1 best solutions below

0
On

Actually, you don't want an alias for a type, you want an alias for a bound.

Please see How to avoid duplication of type bound in Scala

Briefly, you should keep F-bounds wherever you need them. Actually, this is not a code duplication. Type parameter T of Trait, A, B are actually three different type parameters, which can have different bounds.

But theoretically you can abbreviate bounds with a macro annotation, although this is not worth it and generally can be dangerous, because this can be surprising for your team mates, can make debugging more complicated and can confuse your IDE

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

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

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

    def addFBound(tparam: Tree): Tree = tparam match {
      case q"$mods type $name[..$tparams] >: $low <: $high" =>
        val tparamsNames = tparams.map {
          case q"$_ type $nme[..$_] >: $_ <: $_" => nme
        }
        val fBound = tq"Trait[$name[..$tparamsNames]]"
        val high1 = high match {
          case tq"$EmptyTree" => fBound
          case tq"..$withTypes { ..$refinements }" =>
            val withTypes1 = withTypes :+ fBound
            tq"..$withTypes1 { ..$refinements }"
          case tq"$typ" => tq"$typ with $fBound"
        }
        q"$mods type $name[..$tparams] >: $low <: $high1"
    }

    annottees match {
      case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
        val tparams1 = addFBound(tparams.head) :: tparams.tail
        q"""
          $mods class $tpname[..$tparams1] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }
          ..$tail
        """
    }
  }
}

Usage:

trait Trait[T <: Trait[T]] {
  def merge(t: T): T
}

@fbound case class A[T](t: T, i: Int)
@fbound case class B[T](t: T, str: String)

//scalac: {
//  case class A[T <: Trait[T]] extends scala.Product with scala.Serializable {
//    <caseaccessor> <paramaccessor> val t: T = _;
//    <caseaccessor> <paramaccessor> val i: Int = _;
//    def <init>(t: T, i: Int) = {
//      super.<init>();
//      ()
//    }
//  };
//  ()
//}
//scalac: {
//  case class B[T <: Trait[T]] extends scala.Product with scala.Serializable {
//    <caseaccessor> <paramaccessor> val t: T = _;
//    <caseaccessor> <paramaccessor> val str: String = _;
//    def <init>(t: T, str: String) = {
//      super.<init>();
//      ()
//    }
//  };
//  ()
//}