Search code examples
scalafunctional-programmingscala-catsscala-macroscompanion-object

Generate apply methods creating a class


Scala 2.13

I have tons of similar traits of the form

trait SomeTrait[F[_]]{
    def someOp(): F[Unit]
    //...
}

and their implementations

class SomeTraitImpl[F[_]: Sync] extends SomeTrait[F]{
   //...
}

object SomeTrait{
    def apply[F[_]: Sync](): SomeTrait[F] = new SomeTraitImpl[F]()
}

The problem is such companion with the single apply method looks pretty ugly and it is a boilerplate. Is there a way to automate the object generation? Can simulacrum or anything else (a hand written macro annotation?) do that?


Solution

  • You can use macro annotation

    import scala.annotation.{StaticAnnotation, compileTimeOnly}
    import scala.language.experimental.macros
    import scala.reflect.macros.blackbox
    
    @compileTimeOnly("enable macro paradise")
    class implApply extends StaticAnnotation {
      def macroTransform(annottees: Any*): Any = macro ImplApplyMacro.macroTransformImpl
    }
    
    object ImplApplyMacro {
      def macroTransformImpl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
        import c.universe._
    
        def applyMethod(tparams: Seq[Tree], tpname: TypeName): Tree = {
          def tparamNames = tparams.map {
            case q"$_ type $tpname[..$_] = $_" => tq"$tpname"
          }
          q"""def apply[..$tparams]()(implicit sync: Sync[${tparamNames.head}]): $tpname[..$tparamNames] =
                new ${TypeName(tpname + "Impl")}[..$tparamNames]()"""
        }
    
        annottees match {
          case (trt@q"$_ trait $tpname[..$tparams] extends { ..$_ } with ..$_ { $_ => ..$_ }") ::
            q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
            q"""
              $trt
              $mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
                ${applyMethod(tparams, tpname)}
                ..$body
              }
            """
    
          case (trt@q"$_ trait $tpname[..$tparams] extends { ..$_ } with ..$_ { $_ => ..$_ }") :: Nil =>
            q"""
              $trt
              object ${tpname.toTermName} {
                ${applyMethod(tparams, tpname)}
              }
            """
        }
      }
    }
    

    Usage:

    @implApply
    trait SomeTrait[F[_]]{
      def someOp(): F[Unit]
    }
    
    class SomeTraitImpl[F[_]: Sync] extends SomeTrait[F]{
      override def someOp(): F[Unit] = ???
    }
    
    //Warning:scalac: {
    //  object SomeTrait extends scala.AnyRef {
    //    def <init>() = {
    //      super.<init>();
    //      ()
    //    };
    //    def apply[F[_]]()(implicit sync: Sync[F]): SomeTrait[F] = new SomeTraitImpl[F]()
    //  };
    //  ()
    //}