Can I generate Scala code from a template (of sorts)?
Asked Answered
P

3

5

Can I generate Scala code from a template (of sorts)?

I know how to do this in Racket/Scheme/Lisp, but not in Scala. Is this something Scala macros can do?

I want to have a code template where X varies. If I had this code template:

def funcX(a: ArgsX): Try[Seq[RowX]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){Detail.funcX(t, a)}
    case _ => Failure(new MissingThingException)
  }

and tokens Apple and Orange, a macro would take my template, replace the Xs, and produce:

def funcApple(a: ArgsApple): Try[Seq[RowApple]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){Detail.funcApple(t, a)}
    case _ => Failure(new MissingThingException)
  }

def funcOrange(a: ArgsOrange): Try[Seq[RowOrange]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){Detail.funcOrange(t, a)}
    case _ => Failure(new MissingThingException)
  }
Prewar answered 15/10, 2019 at 18:38 Comment(2)
Did you already check generics and typeclasses? If the only difference between funcApple and funcOrange is the funcX call, this can be achieved with typeclasses, there should be no reason to generate two separate functions.Walling
What is the signature of Detail.funcApple or Detail.funcOrange?Circosta
F
6

Try macro annotation with tree transformer

@compileTimeOnly("enable macro paradise")
class generate extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro GenerateMacro.impl
}

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

    val trees = List("Apple", "Orange").map { s =>
      val transformer = new Transformer {
        override def transform(tree: Tree): Tree = tree match {
          case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" if tname.toString.contains("X") =>
            val tname1 = TermName(tname.toString.replace("X", s))
            val tparams1 = tparams.map(super.transform(_))
            val paramss1 = paramss.map(_.map(super.transform(_)))
            val tpt1 = super.transform(tpt)
            val expr1 = super.transform(expr)
            q"$mods def $tname1[..$tparams1](...$paramss1): $tpt1 = $expr1"
          case q"${tname: TermName} " if tname.toString.contains("X") =>
            val tname1 = TermName(tname.toString.replace("X", s))
            q"$tname1"
          case tq"${tpname: TypeName} " if tpname.toString.contains("X") =>
            val tpname1 = TypeName(tpname.toString.replace("X", s))
            tq"$tpname1"
          case q"$expr.$tname " if tname.toString.contains("X") =>
            val expr1 = super.transform(expr)
            val tname1 = TermName(tname.toString.replace("X", s))
            q"$expr1.$tname1"
          case tq"$ref.$tpname " if tpname.toString.contains("X") =>
            val ref1 = super.transform(ref)
            val tpname1 = TypeName(tpname.toString.replace("X", s))
            tq"$ref1.$tpname1"
          case t => super.transform(t)
        }
      }

      transformer.transform(annottees.head)
    }

    q"..$trees"
  }
}
@generate
def funcX(a: ArgsX): Try[Seq[RowX]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){Detail.funcX(t, a)}
    case _ => Failure(new MissingThingException)
  }

//Warning:scalac: {
//  def funcApple(a: ArgsApple): Try[Seq[RowApple]] = w.getThing() match {
//    case Some((t @ (_: Thing))) => w.wrap(t)(Detail.funcApple(t, a))
//    case _ => Failure(new MissingThingException())
//  };
//  def funcOrange(a: ArgsOrange): Try[Seq[RowOrange]] = w.getThing() match {
//    case Some((t @ (_: Thing))) => w.wrap(t)(Detail.funcOrange(t, a))
//    case _ => Failure(new MissingThingException())
//  };
//  ()
//}

Also you can try approach with type class

def func[A <: Args](a: A)(implicit ar: ArgsRows[A]): Try[Seq[ar.R]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){Detail.func(t, a)}
    case _ => Failure(new MissingThingException)
  }

trait ArgsRows[A <: Args] {
  type R <: Row
}
object ArgsRows {
  type Aux[A <: Args, R0 <: Row] = ArgsRows[A] { type R = R0 }

  implicit val apple: Aux[ArgsApple, RowApple] = null
  implicit val orange: Aux[ArgsOrange, RowOrange] = null
}

sealed trait Args
trait ArgsApple extends Args
trait ArgsOrange extends Args

trait Thing

sealed trait Row
trait RowApple extends Row
trait RowOrange extends Row

object Detail {
  def func[A <: Args](t: Thing, a: A)(implicit ar: ArgsRows[A]): ar.R = ???
}

class MissingThingException extends Throwable

trait W {
  def wrap[R <: Row](t: Thing)(r: R): Try[Seq[R]] = ???
  def getThing(): Option[Thing] = ???
}

val w: W = ???
Functionary answered 15/10, 2019 at 20:6 Comment(2)
That looks super hacky! I love it!Hasidism
Wow! Now that's an education!Prewar
D
3

In my opinion, it looks like you could pass your funcX function as a higher-order function. You could also combine it with currying to make a "function factory":

def funcX[A](f: (Thing, A) => RowX)(a: A): Try[Seq[RowX]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){f(t,a)}
    case _ => Failure(new MissingThingException)
 }

Then you could use it to create instances of funcApple or funcOrange:

val funcApple: ArgsApple => Try[Seq[RowX]] = funcX(Detail.funcApple)

val funcOrange: ArgsOrange => Try[Seq[RowX]] = funcX(Detail.funcOrange)


funcApple(argsApple)

funcOrange(argsOrange)

I assumed the signature of Detail.funcApple and Detail.funcOrange is similar to (Thing, X) => RowX, but of course you could use different.

Dispersant answered 15/10, 2019 at 20:9 Comment(0)
F
0

You may not actually need macros to achieve this, you can use a pattern match a generic type like this:

import scala.util.Try

def funcX[A](input :A) :Try[Seq[String]] = input match {
  case x :String => Success(List(s"Input is a string: $input, call Detail.funcApple"))
  case x :Int => Success(List(s"Input is an int, call Detail.funcOrange"))
}

scala> funcX("apple")
res3: scala.util.Try[Seq[String]] = Success(List(Input is a string: apple, call Detail.funcApple))

scala> funcX(11)
res4: scala.util.Try[Seq[String]] = Success(List(Input is an int, call Detail.funcOrange))
Foulk answered 9/12, 2022 at 9:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.