gknauth
gknauth

Reputation: 2390

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

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)
  }

Upvotes: 5

Views: 362

Answers (3)

Philluminati
Philluminati

Reputation: 2789

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))

Upvotes: 0

Dmytro Mitin
Dmytro Mitin

Reputation: 51683

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 = ???

Upvotes: 6

Krzysztof Atłasik
Krzysztof Atłasik

Reputation: 22605

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.

Upvotes: 3

Related Questions