Scala macro to auto generate fluent builders - scala

I am interacting with an external Java API which looks like this:
val obj: SomeBigJavaObj = {
val _obj = new SomeBigJavaObj(p1, p2)
_obj.setFoo(p3)
_obj.setBar(p4)
val somethingElse = {
val _obj2 = new SomethingElse(p5)
_obj2.setBar(p6)
_obj2
}
_obj.setSomethingElse(somethingElse)
_obj
}
Basically the Java API exposes bunch of .setXXXX methods which returns void and sets something. I have no control over these external POJOs.
I would therefore like to write a fluent build Scala macro which inspects the object and creates a builder-pattern type .withXXXX() method for each of the void setXXXX() methods which returns this:
val obj: SomeBigJavaObj =
build(new SomeBigJavaObj(p1, p2))
.withFoo(p3)
.withBar(p4)
.withSomethingElse(
build(new SomethingElse(p5))
.withBar(p6)
.result()
)
.result()
Is this possible? I know I cannot generate new top level objects with def macros so open to other suggestions where I would have the similar ergonomics.

It is not complicated to use macros; just unfriendly to IDE (like:code completion;...);
//edit 1 : support multiple arguments
entity:
public class Hello {
public int a;
public String b;
public void setA(int a) {
this.a = a;
}
public void setB(String b) {
this.b = b;
}
public void setAB(int a , String b){
this.a = a;
this.b = b;
}
}
macro code :
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait BuildWrap[T] {
def result(): T
}
object BuildWrap {
def build[T](t: T): Any = macro BuildWrapImpl.impl[T]
}
class BuildWrapImpl(val c: whitebox.Context) {
import c.universe._
def impl[T: c.WeakTypeTag](t: c.Expr[T]) = {
val tpe = c.weakTypeOf[T]
//get all set member
val setMembers = tpe.members
.filter(_.isMethod)
.filter(_.name.toString.startsWith("set"))
.map(_.asMethod)
.toList
// temp value ;
val valueName = TermName("valueName")
val buildMethods = setMembers.map { member =>
if (member.paramLists.length > 1)
c.abort(c.enclosingPosition,"do not support Currying")
val params = member.paramLists.head
val paramsDef = params.map(e=>q"${e.name.toTermName} : ${e.typeSignature}")
val paramsName = params.map(_.name)
val fieldName = member.name.toString.drop(3)//drop set
val buildFuncName = TermName(s"with$fieldName")
q"def $buildFuncName(..$paramsDef ) = {$valueName.${member.name}(..$paramsName);this} "
}
val result =
q"""new BuildWrap[$tpe] {
private val $valueName = $t
..${buildMethods}
def result() = $valueName
}"""
// debug
println(showCode(result))
result
}
}
test code :
val hello1: Hello = BuildWrap.build(new Hello).withA(1).withB("b").result()
assert(hello1.a == 1)
assert(hello1.b == "b")
val hello2: Hello = BuildWrap.build(new Hello).withAB(1, "b").result()
assert(hello2.a == 1)
assert(hello2.b == "b")

Not a solution, just a very preliminary mock-up
+---------------------------------------------------------+
| |
| D I S C L A I M E R |
| |
| This is a mock-up. It is not type-safe. It relies on |
| runtime reflection (even worse: it relies on |
| Java-reflection!). Do not use this in production. |
| |
| If you can come up with a type-safe solution, I will |
| definitely take a look at it and upvote your answer. |
| |
+---------------------------------------------------------+
You have said explicitly that type-safety is a must, so the code below cannot count as a solution. However, before investigating any further, maybe you would like to experiment with a purely runtime-reflection based implementation, to understand the requirements better. Here is a very quick-and-dirty mock-up implementation:
import scala.language.dynamics
class DynamicBuilder[X](underConstruction: X) extends Dynamic {
val clazz = underConstruction.getClass
def applyDynamic(name: String)(arg: Any): DynamicBuilder[X] = {
if (name.startsWith("with")) {
val propertyName = name.drop(4)
val setterName = "set" + propertyName
clazz.getDeclaredMethods().
find(_.getName == setterName).
fold(throw new IllegalArgumentException("No method " + setterName)) {
m =>
m.invoke(underConstruction, arg.asInstanceOf[java.lang.Object])
this
}
} else {
throw new IllegalArgumentException("Expected 'result' or 'withXYZ'")
}
}
def result(): X = underConstruction
}
object DynamicBuilder {
def build[A](a: A) = new DynamicBuilder[A](a)
}
Once the build-method is imported
import DynamicBuilder.build
and the definitions of the classes that correspond to POJOs are in scope
class SomethingElse(val p5: String) {
var bar: String = _
def setBar(s: String): Unit = { bar = s }
override def toString = s"SomethingElse[p5 = $p5, bar = $bar]"
}
class SomeBigJavaObj(val p1: Float, val p2: Double) {
var foo: Int = 0
var bar: String = _
var sthElse: SomethingElse = _
def setFoo(i: Int): Unit = { foo = i }
def setBar(s: String): Unit = { bar = s }
def setSomethingElse(s: SomethingElse): Unit = { sthElse = s }
override def toString: String =
s"""|SomeBigJavaObj[
| p1 = $p1, p2 = $p2,
| foo = $foo, bar = $bar,
| sthElse = $sthElse
|]""".stripMargin
}
and also all the required variables p1,...,p6 from your example are defined
val p1 = 3.1415f
val p2 = 12345678d
val p3 = 42
val p4 = "BAR"
val p5 = "P5"
val p6 = "b-a-r"
you can use exactly the syntax from your question:
val obj: SomeBigJavaObj =
build(new SomeBigJavaObj(p1, p2))
.withFoo(p3)
.withBar(p4)
.withSomethingElse(
build(new SomethingElse(p5))
.withBar(p6)
.result()
)
.result()
The result looks as follows:
println(obj)
// Output:
// SomeBigJavaObj[
// p1 = 3.1415, p2 = 1.2345678E7,
// foo = 42, bar = BAR,
// sthElse = SomethingElse[p5 = P5, bar = b-a-r]
// ]
For now, the idea is just to see how badly it fails when you try to use it with a somewhat more realistic example. It might turn out that in reality, everything is a little bit more complicated:
Maybe some setters are generic
Maybe some of them use Java wildcards with Java's strange call-site variance
Maybe instead of setters there are some other methods that take multiple parameters as varargs
Maybe there are overloaded setters with same name but different types of arguments.
etc.
I understand that this cannot be a solution, however, I hope that this might be useful as an additional feasibility check, and that it maybe helps to make the requirements a tiny bit more precise, before investing more time and energy in type-safe macro-based solutions.
If this does roughly what you wanted, I might consider to update the answer. If this is not helpful at all, I'll delete the answer.

Related

Scala: How do I set a generic Trait?

I can't set up this generic trait whose parametrized forms can be consumed by a common class/object/method. I have tried different +|-|_ combinations :-)
Update: The first comment below shows that this can work if the Wrapper is also parametrized. Can a non-parametrized Wrapper do the job? Can an object Wrapper do the job? Can some magic combination of +|-|_ and all that give me the same desired result with a non-parametrized Wrapper or object?
case class OldStuff(name: String)
case class NewStuff(id: Int)
trait Poster[T] {
def translate(i: Int):T
}
class NewPoster extends Poster[NewStuff] {
def translate(i: Int):NewStuff = new NewStuff(3)
}
class OldPoster extends Poster[OldStuff] {
def translate(i: Int):OldStuff = new OldStuff("A" * 3)
}
val old = new OldPoster()
// so far so good
class Wrapper{
var poster: Poster[_] = null
def setter(p: Poster[_]) = {poster = p }
def prepare_input[A]( ) = {
val i: Int = 5
println(poster.translate(i))
}
}
val w= new Wrapper()
val old = new OldPoster()
w.setter(old)
scala> w.setter(old)
<console>:58: error: type mismatch;
found : OldPoster
required: Poster[_]
w.setter(old)
First, I don't see such error with Scala 2.11.
Then, would be better to avoid erasure by Poster[_]:
class Wrapper[T] {
var poster: Poster[T] = null
def setter(p: Poster[T]) = {poster = p }
def prepare_input() = {
val i: Int = 5
println(poster.translate(i))
}
}
val w= new Wrapper[OldStuff]()
val old = new OldPoster()
w.setter(old)
Finally, not using mutability would make the code more predictable against concurrency.
class Wrapper[T](poster: Poster[T]) {
def prepare_input() = {
val i: Int = 5
println(poster.translate(i))
}
}
val w = new Wrapper(new OldPoster())

Scala reflect string to singleton object

I'm looking for a way to convert a Scala singleton object given as a string (for example: package1.Main) to the actual instance of Main, so that I can invoke methods on it.
Example of the problem:
package x {
object Main extends App {
val objectPath: String = io.StdIn.readLine("Give an object: ") // user enters: x.B
// how to convert the objectPath (String) to a variable that references singleton B?
val b1: A = magicallyConvert1(objectPath)
b1.hi()
val b2: B.type = magicallyConvert2(objectPath)
b2.extra()
}
trait A {
def hi() = {}
}
object B extends A {
def extra() = {}
}
}
How can the magicallyConvert1 and magicallyConvert2 functions be implemented?
For a normal class, this can be done using something like:
val b: A = Class.forName("x.B").newInstance().asInstanceOf[A]
But I found a solution for singletons, using Java reflections:
A singleton is accesible in Java under the name:
package.SingletonName$.MODULE$
So you have to append "$.MODULE$", which is a static field.
So we can use standard Java reflections to get it.
So the solution is:
def magicallyConvert1(objectPath: String) = {
val clz = Class.forName(objectPath + "$")
val field = clz.getField("MODULE$")
val b: A = field.get(null).asInstanceOf[A]
b
}
def magicallyConvert2(objectPath: String) = {
val clz = Class.forName(objectPath + "$")
val field = clz.getField("MODULE$")
val b: B.type = field.get(null).asInstanceOf[B.type]
b
}
But it would be interesting to still see a solution with Scala-Reflect en Scala-Meta.
take a look at scalameta http://scalameta.org it does what you want and more

How do I get an item in a list that matches a certain condition?

I have the following sample code :
package models
import java.util.concurrent.atomic.AtomicInteger
import scala.collection.mutable.ArrayBuffer
case class Task(id: Int, label: String)
object Task {
private val buffer = new ArrayBuffer[Task]
private val incrementer = new AtomicInteger()
def all(): List[Task] = buffer.toList
def create(label: String): Int = {
val newId = incrementer.incrementAndGet()
buffer += new Task(newId, label)
newId
}
def delete(id: Int): Boolean = {
// TODO : add code
}
}
In method delete I need to find a Task that has id equal to the parameter id and if one is found I need to remove it from the collection and return true from the method. Otherwise (if none is found) I should just return false.
I know how to do this in an imperative language such as C# or Java but Scala stumps me..
PS : The code is strictly used to understand the language and the platform, it sucks too much to be pushed in production. Don't worry.
This is one possible solution, however in this case I think it's also possible to switch to var + immutable ArrayBuffer and use filter. Also note that this code is not thread safe
import java.util.concurrent.atomic.AtomicInteger
import scala.collection.mutable.ArrayBuffer
case class Task(id: Int, label: String)
object Task {
private val buffer = new ArrayBuffer[Task]
private val incrementer = new AtomicInteger()
def all(): List[Task] = buffer.toList
def create(label: String): Int = {
val newId = incrementer.incrementAndGet()
buffer.append(Task(newId, label))
newId
}
def delete(id: Int): Boolean = {
buffer.
find(_.id == id). // find task by id
map(buffer -= _). // remove it from buffer
exists(_ => true) // the same as: map(_ => true).getOrElse(false)
}
}
val id1 = Task.create("aaa")
val id2 = Task.create("bbb")
println(s"Id1 = $id1 Id2 = $id2")
println(s"All = ${Task.all()}")
val deleted = Task.delete(id1)
println(s"Deleted = $deleted")
println(s"All = ${Task.all()}")
println(s"Not Deleted = ${Task.delete(123)}")

Define function for extension of abstract class

I'm having trouble with type mismatches when trying to write a function that takes as input (and output) an object that extends an abstract class.
Here is my abstract class:
abstract class Agent {
type geneType
var genome: Array[geneType]
}
Here is my function:
def slice[T <: Agent](parentA: T, parentB: T):(T, T) = {
val genomeSize = parentA.genome.length
// Initialize children as identical to parents at first.
val childA = parentA
val childB = parentB
// the value 'index' is sampled randomly between 0 and
// the length of the genome, less 1.
// This code omitted for simplicity.
val index;
val pAslice1 = parentA.genome.slice(0, index + 1)
val pBslice1 = parentB.genome.slice(index + 1, genomeSize)
val genomeA = Array.concat(pAslice1, pBslice1)
childA.genome = genomeA
// And similary for childB.
// ...
// ...
return (childA, childB)
}
I'm receiving an error (I'm running this with sbt, by the way) as follows:
[error] .......... type mismatch;
[error] found : Array[parentA.geneType]
[error] required: Array[T#geneType]
I'm not sure what the problem is, as I'm new to abstract classes, generic type parametrization, and probably other relevant concepts whose names I don't know.
In your construction it is well possible that parentA and parentB are different types, T only gives you an upper bound (they must be at least as specific as T). Arrays are invariant in their element type, thus you cannot exchange the elements in a sound way here.
A second problem with your code is that you are returning objects of type T, but actually you are mutating the input arguments. Either you want mutation, then declare the method's return type Unit to make that clear; or create new instances of T and make Agent immutable. It depends on your performance requirements, but I would always try the immutable variant first, because it is easier to reason about.
Here is mutable variant. Note that because arrays are special objects on the JVM (no type erasure happening), you need to provide a so-called class-tag for them as well:
abstract class Agent {
type geneType
var genome: Array[geneType]
implicit def geneTag: reflect.ClassTag[geneType]
}
def slice[A](parentA: Agent { type geneType = A },
parentB: Agent { type geneType = A }): Unit = {
val genomeSize = parentA.genome.length
require (parentB.genome.length == genomeSize)
import parentA.geneTag
val index = (math.random * genomeSize + 0.5).toInt
val (aInit, aTail) = parentA.genome.splitAt(index)
val (bInit, bTail) = parentB.genome.splitAt(index)
val genomeA = Array.concat(aInit, bTail)
val genomeB = Array.concat(bInit, aTail)
parentA.genome = genomeA
parentB.genome = genomeB
}
Here you require that parentA and parentB share an exactly defined gene-type A. You can define a type alias to simplify specifying that type:
type AgentT[A] = Agent { type geneType = A }
def slice[A](parentA: AgentT[A], parentB: AgentT[A]): Unit = ...
To preserve the parents and create new children, the easiest would be to add a copy method to the Agent class:
abstract class Agent {
type geneType
var genome: Array[geneType]
implicit def geneTag: reflect.ClassTag[geneType]
def copy(newGenome: Array[geneType]): AgentT[geneType]
}
type AgentT[A] = Agent { type geneType = A }
def slice[A](parentA: AgentT[A], parentB: AgentT[A]): (AgentT[A], AgentT[A]) = {
val genomeSize = parentA.genome.length
require (parentB.genome.length == genomeSize)
import parentA.geneTag
val index = (math.random * genomeSize + 0.5).toInt
val (aInit, aTail) = parentA.genome.splitAt(index)
val (bInit, bTail) = parentB.genome.splitAt(index)
val genomeA = Array.concat(aInit, bTail)
val genomeB = Array.concat(bInit, aTail)
(parentA.copy(genomeA), parentB.copy(genomeB))
}
If you don't need to squeeze the last bits of performance, you could use an immutable collection such as Vector instead of Array.
case class Agent[A](genome: Vector[A]) {
def size = genome.size
}
def slice[A](parentA: Agent[A], parentB: Agent[A]): (Agent[A], Agent[A]) = {
val genomeSize = parentA.size
require (parentB.size == genomeSize)
val index = (math.random * genomeSize + 0.5).toInt
val (aInit, aTail) = parentA.genome.splitAt(index)
val (bInit, bTail) = parentB.genome.splitAt(index)
val genomeA = aInit ++ bTail
val genomeB = bInit ++ aTail
(parentA.copy(genomeA), parentB.copy(genomeB))
}

Compile String to AST inside CompilerPlugin?

I would like to create a templating plugin and as the first step convert an arbitrary string to it's "compiled" AST representation (as the scala interpreter does, I guess). So a compiler plugin could e.g assign someString to "HELLO WORLD":
#StringAnnotation("""("hello world").toString.toUpperCase""")
var someString = ""
My current first shot plugin does in short:
runafter parser
create a new representation only compiler and a VirtualFile with the annotation content
compile and print unit.body
see: http://paste.pocoo.org/show/326025/
a)
Right now, "object o{val x = 0}" returns an AST, but e.g. "var x = 1+ 2" doesn't because it wouldn't be a valid .scala file. How can I fix this?
b)
Is onlyPresentation a good choice? Should I instead overriding computeInternalPhases with the appropriate phases or use -Ystop:phase?
c)
Is it possible to bind the environment of the outer compiler to the inner one, so that e.g.
var x = _
(...)
#StringAnnotation("x += 3")
would work?
I found following code[1] using an interpreter and one variable which does something similar:
Interpreter interpreter = new Interpreter(settings);
String[] context = { "FOO" };
interpreter.bind("context", "Array[String]", context);
interpreter
.interpret("de.tutorials.scala2.Test.main(context)");
context[0] = "BAR";
interpreter
.interpret("de.tutorials.scala2.Test.main(context)");
[1] http://www.tutorials.de/java/320639-beispiel-zur-einbindung-des-scala-interpreters-kompilierte-scala-anwendungen.html#post1653884
thanks
Complete Code:
class AnnotationsPI(val global: Global) extends Plugin {
import global._
val name = "a_plugins::AnnotationsPI" //a_ to run before namer
val description = "AST Trans PI"
val components = List[PluginComponent](Component)
private object Component extends PluginComponent with Transform with TypingTransformers with TreeDSL {
val global: AnnotationsPI.this.global.type = AnnotationsPI.this.global
val runsAfter = List[String]("parser");
val phaseName = AnnotationsPI.this.name
def newTransformer(unit: CompilationUnit) = {
new AnnotationsTransformer(unit)
}
val SaTpe = "StringAnnotation".toTypeName
class AnnotationsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
/** When using <code>preTransform</code>, each node is
* visited before its children.
*/
def preTransform(tree: Tree): Tree = tree match {
case anno#ValDef(Modifiers(_, _, List(Apply(Select(New(Ident(SaTpe)), _), List(Literal(Constant(a))))), _), b, c, d) => //Apply(Select(New(Ident(SaTpe)), /*nme.CONSTRUCTOR*/_), /*List(x)*/x)
val str = a.toString
val strArr = str.getBytes("UTF-8")
import scala.tools.nsc.{ Global, Settings, SubComponent }
import scala.tools.nsc.reporters.{ ConsoleReporter, Reporter }
val settings = new Settings()
val compiler = new Global(settings, new ConsoleReporter(settings)) {
override def onlyPresentation = true
}
val run = new compiler.Run
val vfName = "Script.scala"
var vfile = new scala.tools.nsc.io.VirtualFile(vfName)
val os = vfile.output
os.write(strArr, 0, str.size) // void write(byte[] b, int off, int len)
os.close
new scala.tools.nsc.util.BatchSourceFile(vfName, str)
run.compileFiles(vfile :: Nil)
for (unit <- run.units) {
println("Unit: " + unit)
println("Body:\n" + unit.body)
}
tree
case _ =>
tree
}
override def transform(tree: Tree): Tree = {
super.transform(preTransform(tree))
}
}
}
I don't know if this helps you much, but instead of fiddling with the Interpreter, you can use treeFrom( aString ) which is part of the scala-refactoring project ( http://scala-refactoring.org/ ). doesn't answer your question about cross-bindings, though...