I have a List with all the methods in it example
List("method1","method2","method3")
and I have the definition for all those methods mentioned in above list
def method1(){ //definition}
def method2(){ //definition}
def method3(){ //definition}
Instead of specifying those functions to run one by one like below
method1()
method2()
method3()
I want to loop through List("method1","method2","method3") and execute those methods
If all of the target methods have the same type profile (same argument number/types, same return type) ...
def method1() = println("one")
def method2() = println("two")
def method3() = println("three")
... then all you need is a String-to-method translator.
val translate : Map[String,Function0[Unit]] =
Map("method3" -> method3
,"method1" -> method1
,"method2" -> method2)
usage:
List("method1","method2","method3")
.foreach(translate(_)())
//one
//two
//three
If method names are known at compile time you can write a macro
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def executeAll(methodNames: List[String]): Unit = macro executeAllImpl
def executeAllImpl(c: blackbox.Context)(methodNames: c.Tree): c.Tree = {
import c.universe._
c.eval(c.Expr[List[String]](c.untypecheck(methodNames)))
.foldLeft[Tree](q"()")((tree, methodName) => q"$tree; ${TermName(methodName)}()")
}
def method1(): Unit = println(1)
def method2(): Unit = println(2)
def method3(): Unit = println(3)
executeAll(List("method1","method2","method3")) //1 2 3
//Warning:scalac: {
// {
// {
// ();
// method1()
// };
// method2()
// };
// method3()
//}
or
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def executeAll(methodNames: List[String]): Unit = macro executeAllImpl
def executeAllImpl(c: blackbox.Context)(methodNames: c.Tree): c.Tree = {
import c.universe._
val calls = c.eval(c.Expr[List[String]](c.untypecheck(methodNames)))
.map(methodName => q"${TermName(methodName)}()")
q"..$calls"
}
def method1(): Unit = println(1)
def method2(): Unit = println(2)
def method3(): Unit = println(3)
executeAll(List("method1","method2","method3")) //1 2 3
//Warning:scalac: {
// method1();
// method2();
// method3()
//}
(the tree generated is slightly different but result of execution is the same).
Or if method names are known only at runtime you can use either Scala reflection
def executeAll(methodNames: List[String]): Unit = {
import scala.reflect.runtime
import scala.reflect.runtime.universe._
methodNames.foreach(methodName => {
val methodSymbol = typeOf[SomeObject.type].decl(TermName(methodName)).asMethod
runtime.currentMirror.reflect(SomeObject).reflectMethod(methodSymbol)()
})
}
object SomeObject {
def method1(): Unit = println(1)
def method2(): Unit = println(2)
def method3(): Unit = println(3)
}
val methodNames = List("method1", "method2", "method3")
executeAll(methodNames) //1 2 3
or Java reflection
def executeAll(methodNames: List[String]): Unit =
methodNames.foreach(methodName =>
SomeObject.getClass.getMethod(methodName).invoke(SomeObject)
)
object SomeObject {
def method1(): Unit = println(1)
def method2(): Unit = println(2)
def method3(): Unit = println(3)
}
val methodNames = List("method1", "method2", "method3")
executeAll(methodNames) //1 2 3
Related
I have a macro that I use to generate some code to call methods dynamically. The macro is more complex than this, but for simplicity let's say it works something like this
def myMacro[T]: Seq[MethodName]
so than when called on
class Hello {
def one(a: Int, b: UserId): String = a.toString + b.id
def two(c: Option[Int]): String = ""
def three(d: Seq[Int], f: Set[Int]): String = ""
}
println(myMacro[Hello]) // Seq("one", "two", "three")
I need this macro to generate code for an internal framework we use at Candu, but I need to be able to call it from the parent's class. So what I want to achieve is:
trait Superclass {
def aFakeMethod: String = ""
val methods = myMacro[Self] // FIXME! self is not defined here...
}
class Hello extends Superclass {
def one(a: Int, b: UserId): String = a.toString + b.id
def two(c: Option[Int]): String = ""
def three(d: Seq[Int], f: Set[Int]): String = ""
}
val hi = new Hello
println(hi.methods) // Seq("one", "two", "three")
Because the high number of classes in the framework, modifying the api between Hello and Superclass is very expansive. So I would need a way to do this without changing code in Hello
Any suggestions on how this could be achieved?
If myMacro worked outside Hello it should work inside Superclass as well
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def myMacro[T]: Seq[String] = macro impl[T]
def impl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val methodNames = weakTypeOf[T].decls
.filter(symb => symb.isMethod && !symb.isConstructor)
.map(_.name.toString).toList
val methodNamesTree = methodNames.foldRight[Tree](q"Nil")((name, names) => q"$name :: $names")
q"..$methodNamesTree"
}
Usage:
sealed trait Superclass {
def aFakeMethod: String = ""
val methods = myMacro[Hello]
}
val hi = new Hello
println(hi.methods) // List("one", "two", "three")
If for some reason you can't use the name of Hello you can try to make Superclass sealed and use knownDirectSubclasses
def myMacro1(): Seq[String] = macro impl1
def impl1(c: blackbox.Context)(): c.Tree = {
import c.universe._
val child = c.enclosingClass.symbol.asClass.knownDirectSubclasses.head
q"myMacro[$child]"
}
Usage:
sealed trait Superclass {
def aFakeMethod: String = ""
val methods = myMacro1()
}
val hi = new Hello
println(hi.methods) // List("one", "two", "three")
Or you can replace deprecated c.enclosingClass.symbol.asClass with c.internal.enclosingOwner.owner.asClass (now enclosingOwner is val methods, enclosingOwner.owner is trait Superclass).
If you can't make Superclass sealed try to traverse all classes and look for those extending Superclass
def myMacro2(): Seq[Seq[String]] = macro impl2
def impl2(c: blackbox.Context)(): c.Tree = {
import c.universe._
def treeSymbol(tree: Tree): Symbol = c.typecheck(tree, mode = c.TYPEmode).symbol
val enclosingClassSymbol = c.internal.enclosingOwner.owner
def isEnclosingClass(tree: Tree): Boolean = treeSymbol(tree) == enclosingClassSymbol
var methodss = Seq[Seq[String]]()
val traverser = new Traverser {
override def traverse(tree: Tree): Unit = {
tree match {
case q"$_ class $_[..$_] $_(...$_) extends { ..$_ } with ..$parents { $_ => ..$stats }"
if parents.exists(isEnclosingClass(_)) =>
val methods = stats.collect {
case q"$_ def $tname[..$_](...$_): $_ = $_" => tname.toString
}
methodss :+= methods
case _ => ()
}
super.traverse(tree)
}
}
c.enclosingRun.units.foreach(unit => traverser.traverse(unit.body))
def namesToTree[A: Liftable](names: Seq[A]): Tree =
names.foldRight[Tree](q"Seq()")((name, names) => q"$name +: $names")
namesToTree[Tree](methodss.map(namesToTree[String](_)))
}
Usage:
trait Superclass {
def aFakeMethod: String = ""
val methods = myMacro2()
}
class Hello1 extends Superclass {
def four = ???
def five = ???
}
class Hello extends Superclass {
def one(a: Int, b: UserId): String = a.toString + b.id
def two(c: Option[Int]): String = ""
def three(d: Seq[Int], f: Set[Int]): String = ""
}
val hi = new Hello
println(hi.methods) // List(List("four", "five"), List("one", "two", "three"))
I want to implement generic and typesafe domain repository. Say I have
trait Repo[Value] {
def put(value: Value): Unit
}
case class IntRepo extends Repo[Int] {
override def put(value: Int): Unit = ???
}
case class StringRepo extends Repo[String] {
override def put(value: String): Unit = ???
}
case class DomainRepo(intRepo: IntRepo, stringRepo: StringRepo) {
def putAll[?](values: ?*): Unit // what type should be here?
}
As result I want to have following api:
domainRepo.putAll(1, 2, 3, "foo", "bar") //Should work
domainRepo.putAll(1, 2, true, "foo") // should not compile because of boolean value
The question is How to achieve this?
so, I see only one way to make it typesafe. It's to do pattern matching on Any type like
def putAll(values: Seq[Any]) => Unit = values.foreach {
case str: String => stringRepo.put(str)
case int: Int => intRepo.put(int)
case _ => throw RuntimeException // Ha-Ha
}
but what if I would have 10000 of types here? it would be a mess!
another not clear for me approach for now is to use dotty type | (or) like following:
type T = Int | String | 10000 other types // wouldn't be a mess?
def putAll(t: T*)(implicit r1: Repo[Int], r2: Repo[String] ...) {
val myTargetRepo = implicitly[Repo[T]] // would not work
}
so, what do you think? is it even possible?
the easies way I've saw was
Map[Class[_], Repo[_]]
but this way allows to do a lot of errors
It seems you are looking for a type class
trait Repo[Value] {
def put(value: Value): Unit
}
implicit val intRepo: Repo[Int] = new Repo[Int] {
override def put(value: Int): Unit = ???
}
implicit val stringRepo: Repo[String] = new Repo[String] {
override def put(value: String): Unit = ???
}
case object DomainRepo {
def putAll[Value](value: Value)(implicit repo: Repo[Value]): Unit = repo.put(value)
}
If you want domainRepo.putAll(1, 2, 3, "foo", "bar") to compile and domainRepo.putAll(1, 2, true, "foo") not to compile, you can try to use heterogeneous collection (HList).
import shapeless.{HList, HNil, ::, Poly1}
import shapeless.ops.hlist.Mapper
trait Repo[Value] {
def put(value: Value): Unit
}
implicit val intRepo: Repo[Int] = new Repo[Int] {
override def put(value: Int): Unit = ???
}
implicit val stringRepo: Repo[String] = new Repo[String] {
override def put(value: String): Unit = ???
}
case object DomainRepo {
def put[Value](value: Value)(implicit repo: Repo[Value]): Unit = repo.put(value)
object putPoly extends Poly1 {
implicit def cse[Value: Repo]: Case.Aux[Value, Unit] = at(put(_))
}
def putAll[Values <: HList](values: Values)(implicit
mapper: Mapper[putPoly.type, Values]): Unit = mapper(values)
}
DomainRepo.putAll(1 :: 2 :: 3 :: "foo" :: "bar" :: HNil)
// DomainRepo.putAll(1 :: 2 :: true :: "foo" :: HNil) // doesn't compile
I use Scala macros and match Apply and I would like to get fully qualified name of the function which is called.
Examples:
println("") -> scala.Predef.println
scala.Predef.println("") -> scala.Predef.println
class Abc {
def met(): Unit = ???
}
case class X {
def met(): Unit = ???
def abc(): Abc = ???
}
val a = new Abc()
val x = new Abc()
a.met() -> Abc.met
new Abc().met() -> Abc.met
X() -> X.apply
X().met() -> X.met
x.met() -> X.met
x.abc.met() -> Abc.met
On the left side is what I have in code and on the right side after arrow is what I want to get. Is it possible? And how?
Here is the macro:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
object ExampleMacro {
final val useFullyQualifiedName = false
def methodName(param: Any): String = macro debugParameters_Impl
def debugParameters_Impl(c: blackbox.Context)(param: c.Expr[Any]): c.Expr[String] = {
import c.universe._
param.tree match {
case Apply(Select(t, TermName(methodName)), _) =>
val baseClass = t.tpe.resultType.baseClasses.head // there may be a better way than this line
val className = if (useFullyQualifiedName) baseClass.fullName else baseClass.name
c.Expr[String](Literal(Constant(className + "." + methodName)))
case _ => sys.error("Not a method call: " + show(param.tree))
}
}
}
Usage of the macro:
object Main {
def main(args: Array[String]): Unit = {
class Abc {
def met(): Unit = ???
}
case class X() {
def met(): Unit = ???
def abc(): Abc = ???
}
val a = new Abc()
val x = X()
import sk.ygor.stackoverflow.q53326545.macros.ExampleMacro.methodName
println(methodName(Main.main(Array("foo", "bar"))))
println(methodName(a.met()))
println(methodName(new Abc().met()))
println(methodName(X()))
println(methodName(X().met()))
println(methodName(x.met()))
println(methodName(x.abc().met()))
println(methodName("a".getClass))
}
}
Source code for this example contains following:
it is a multi module SBT project, because macros have to be in a separate compilation unit than classes, which use the macro
macro modules depends explicitly on libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value,
Going from the following macro implementation, which is working as expected, i would like to remove the hard coded Account and replace it with a variable beeing passed as type parameter T:c.WeakTypeTag to the macro by the caller.
how should the macro be modified so that i can pass any type T?
`
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
import com.xy.iws.model.Model._
import com.xy.iws.repo.dao.DAO._
import com.xy.iws.service.run
import com.xy.iws.service.Api
import com.xy.iws.service.Request._
object makeService {
def make[T]:Api[Account] = macro makeImpl[T]
def makeImpl[T:c.WeakTypeTag](c: Context): c.Expr[Api[Account]] = c.universe.reify {
//val source = c.weakTypeOf[T]
import com.xy.iws.service.Request._
implicit val api = new Api[Account] {
def create():Int = {run.runG[Int](Create[Account])}
def all(): List[Account] = run.runG[List[Account]](new FindAll[Account])
def insert(model: List[Account]) = run.runG[Int](new Insert[Account](model))
def findSome(id: String): List[Account] = run.runG[List[Account]](new FindSome[Account](id))
def find(id: String): List[Account] = run.runG[List[Account]](new Find[Account](id))
def update(item: Account): List[Account] = {
val i = run.runG[List[Account]](new Find[Account](item.id))
if (i.size > 0) {
val updateAccount = run.runG[Int](new Update[Account](item.id, item.name))
}else {
insert(List(item))
}
run.runG[List[Account]](new FindAll[Account])
}
def delete(id: String): List[Account] = {
run.runG[Int](new Delete[Account](id))
run.runG[List[Account]](new FindAll[Account])
}
}
api
}
}
`
maybe you can use quasiquotes
abstract class Api[T] {
def a: Int
def b: Int
def t: List[T]
}
object Reify {
def apply[T](initValue: T): Api[T] = macro impl[T]
def impl[T: c.WeakTypeTag](c: Context)(initValue: c.Expr[T]): c.Tree = {
import c.universe._
val t = c.weakTypeOf[T].typeSymbol.name.toTypeName
q"""
val api = new Api[$t] {
def a = 1
def b = 2
override def t= List(${initValue})
}
api
"""
}
}
---use like this
object ReifyUsing extends App {
import macross.Reify
import macross.Api
println(Reify.apply[String]("String").t)
}
Thank you that was the hint i was looking for as answer to my question:
How to pass pass parameter in particular type parameter to the implementation of a macro?
Short answer: Use quasiquote
See the implementation below that did the job
`
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
import com.xy.iws.model.Model._
import com.xy.iws.repo.dao.DAO._
object makeService {
import com.xy.iws.service.Api
import com.xy.iws.service.Request
def make[T]:Api[T] = macro makeImpl[T]
def makeImpl[T:c.WeakTypeTag](c: Context): c.Tree = {
import c.universe._
val t = c.weakTypeOf[T].typeSymbol.name.toTypeName
q"""
implicit val api = new Api[$t] {
override def create():Int = {run.runG[Int](Create[$t])}
override def all(): List[$t] = run.runG[List[$t]](new FindAll[$t])
override def insert(model: List[$t]) = run.runG[Int](new Insert[$t](model))
override def findSome(id: String): List[$t] = run.runG[List[$t]](new FindSome[$t](id))
override def find(id: String): List[$t] = run.runG[List[$t]](new Find[$t](id))
override def update(item: $t): List[$t] = {
val i = run.runG[List[$t]](new Find[$t](item.id))
if (i.size > 0) {
run.runG[Int](new Update[$t](item.id, item.name))
}else {
insert(List(item))
}
run.runG[List[$t]](new FindAll[$t])
}
override def delete(id: String): List[$t] = {
run.runG[Int](new Delete[$t](id))
run.runG[List[$t]](new FindAll[$t])
}
}
api
"""
}
}
`
and i use the macro like this:
object Main {
def main(args: Array[String])
{
import com.xy.iws.service.Api
println(makeService.make[Account].all +" account objects")
println(makeService.make[Article].all +" article objects")
println(makeService.make[Supplier].all +" supplier objects")
}
}
I'm having some problems with a macro I've written to help me log metrics represented as case class instances to to InfluxDB. I presume I'm having a type erasure problem and that the tyep parameter T is getting lost, but I'm not entirely sure what's going on. (This is also my first exposure to Scala macros.)
import scala.language.experimental.macros
import play.api.libs.json.{JsNumber, JsString, JsObject, JsArray}
abstract class Metric[T] {
def series: String
def jsFields: JsArray = macro MetricsMacros.jsFields[T]
def jsValues: JsArray = macro MetricsMacros.jsValues[T]
}
object Metrics {
case class LoggedMetric(timestamp: Long, series: String, fields: JsArray, values: JsArray)
case object Kick
def log[T](metric: Metric[T]): Unit = {
println(LoggedMetric(
System.currentTimeMillis,
metric.series,
metric.jsFields,
metric.jsValues
))
}
}
And here's an example metric case class:
case class SessionCountMetric(a: Int, b: String) extends Metric[SessionCountMetric] {
val series = "sessioncount"
}
Here's what happens when I try to log it:
scala> val m = SessionCountMetric(1, "a")
m: com.confabulous.deva.SessionCountMetric = SessionCountMetric(1,a)
scala> Metrics.log(m)
LoggedMetric(1411450638296,sessioncount,[],[])
Even though the macro itself seems to work fine:
scala> m.jsFields
res1: play.api.libs.json.JsArray = ["a","b"]
scala> m.jsValues
res2: play.api.libs.json.JsArray = [1,"a"]
Here's the actual macro itself:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
object MetricsMacros {
private def fieldNames[T: c.WeakTypeTag](c: Context)= {
val tpe = c.weakTypeOf[T]
tpe.decls.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.asTerm.name
}
}
def jsFields[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val names = fieldNames[T](c)
Apply(
q"play.api.libs.json.Json.arr",
names.map(name => Literal(Constant(name.toString))).toList
)
}
def jsValues[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val names = fieldNames[T](c)
Apply(
q"play.api.libs.json.Json.arr",
names.map(name => q"${c.prefix.tree}.$name").toList
)
}
}
Update
I tried Eugene's second suggestion like this:
abstract class Metric[T] {
def series: String
}
trait MetricSerializer[T] {
def fields: Seq[String]
def values(metric: T): Seq[Any]
}
object MetricSerializer {
implicit def materializeSerializer[T]: MetricSerializer[T] = macro MetricsMacros.materializeSerializer[T]
}
object Metrics {
def log[T: MetricSerializer](metric: T): Unit = {
val serializer = implicitly[MetricSerializer[T]]
println(serializer.fields)
println(serializer.values(metric))
}
}
with the macro now looking like this:
object MetricsMacros {
def materializeSerializer[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val tpe = c.weakTypeOf[T]
val names = tpe.decls.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.asTerm.name
}
val fields = Apply(
q"Seq",
names.map(name => Literal(Constant(name.toString))).toList
)
val values = Apply(
q"Seq",
names.map(name => q"metric.$name").toList
)
q"""
new MetricSerializer[$tpe] {
def fields = $fields
def values(metric: Metric[$tpe]) = $values
}
"""
}
}
However, when I call Metrics.log -- specifically when it calls implicitly[MetricSerializer[T]] I get the following error:
error: value a is not a member of com.confabulous.deva.Metric[com.confabulous.deva.SessionCountMetric]
Why is it trying to use Metric[com.confabulous.deva.SessionCountMetric] instead of SessionCountMetric?
Conclusion
Fixed it.
def values(metric: Metric[$tpe]) = $values
should have been
def values(metric: $tpe) = $values
You're in a situation that's very close to one described in a recent question: scala macros: defer type inference.
As things stand right now, you'll have to turn log into a macro. An alternative would also to turn Metric.jsFields and Metric.jsValues into JsFieldable and JsValuable type classes materialized by implicit macros at callsites of log (http://docs.scala-lang.org/overviews/macros/implicits.html).