Scala macro refactoring - scala

I have written JDBC select and insert mapping using scala macro functionality.
gist
As you can see in the gist there is a lot of duplicated code...
I can't extract case classes ClassInfo and ColumnInfo outside of method scope because of c.universe._ import because I'm using c.Type.
I have tried using scala.reflect.runtime.universe._ but then I get some mirror exceptions and suddenly type comparison doesn't work anymore - I don't get ClassSymbol anymore but SynchronizedClassSymbol and I have to compare it using fullName and it suddenly get's very messy...
So my question is:
I would like to refactor:
case class ClassInfo
case class ColumnInfo
mapGet
mapSet
def getBaseClass(tp: c.Type): c.Type
def getClassInfo(tp: c.Type): Option[ClassInfo]
def getConstructorParameters(tp: c.Type): Seq[ColumnInfo]
def selectFullTermName(sym: c.Symbol): c.Tree
Outside of methods selectImpl and insertImpl but I just don't know how :(
I tried for example with method
def getBaseClass(tp: c.Type): c.Type
To put it outside like this:
def getBaseClass(c: whitebox.Context)(tp: c.Type) = {
import c.universe._
if (tp.baseClasses.map(_.fullName).contains(symbolOf[Option[_]].fullName))
tp.typeArgs.head
else
tp
}
But then where I call it like
getBaseClass(c)(x.typeSignature)
I get an error:
Type missmatch, expected: c.Type, actual: whitebox.Context#Type
And it drives me crazy =)
I'm quite new at Scala so if you have any other comments about how to write macros - if I have made any mistakes - please let me know=)

Turns out it works like this:
def getBaseClass(c: whitebox.Context)(tp: c.Type) = {
import c.universe._
if (tp.baseClasses.map(_.fullName).contains(symbolOf[Option[_]].fullName))
tp.typeArgs.head
else
tp
}
It compiles but IntelliJ reports error:
Type missmatch, expected: c.Type, actual: whitebox.Context#Type
So it's a IDE bug =)

Related

How to apply sequence function to List of ValidatedNel in cats?

I have the following code
sealed trait DomainValidation {
def errorMessage: String
}
type ValidationResult[A] = ValidatedNel[DomainValidation, A]
val ai:ValidationResult[String] = "big".validNel
val bi:ValidationResult[String] = "leboski".validNel
val l = List(ai,bi)
I want to convert l to ValidationResult[List[String]]. I came across sequence functionality but I am unable to use cats sequence as some implicit has to be there which knows how to handle ValidationResult[A]. But I am unable figure out what exactly is needed. I wrote the following
object helper {
implicit class hello[A](l: List[ValidationResult[A]]) {
def mysequence: ValidationResult[List[A]] = {
val m = l.collect { case Invalid(a) => Invalid(a) }
if (m.isEmpty) l.map { case Valid(a) => a }.validNel
else /* merge the NonEmpty Lists */
}
}
}
I am able to do l.mysequence. But how do I use cats sequence.
PS: I am a scala beginner. Having a hard time learning :). Forgive for any incorrect mentions.
The following should work as expected on Scala 2.12:
import cats.data.ValidatedNel, cats.syntax.validated._
// Your code:
sealed trait DomainValidation {
def errorMessage: String
}
type ValidationResult[A] = ValidatedNel[DomainValidation, A]
val ai:ValidationResult[String] = "big".validNel
val bi:ValidationResult[String] = "leboski".validNel
val l = List(ai,bi)
And then:
scala> import cats.instances.list._, cats.syntax.traverse._
import cats.instances.list._
import cats.syntax.traverse._
scala> l.sequence
res0: ValidationResult[List[String]] = Valid(List(big, leboski))
You don't show your code or explain what's not working, so it's hard to diagnose your issue, but it's likely to be one of the following problems:
You're on Scala 2.11, where .sequence requires you to enable -Ypartial-unification in your compiler options. If you're using sbt, you can do this by adding scalacOptions += "-Ypartial-unification" to your build.sbt (assuming you're on 2.11.9+).
You've omitted one of the necessary imports. You need at least the Traverse instance for List and the syntax for Traverse. The example code above includes the two imports you need, or you can just import cats.implicits._ and make your life a little easier.
If it's not one of these two things, you'll probably need to include more detail in your question for us to be able to help.

Option and null in Scala

If I have the following function:
def getOrNull[T >: Null](f: => T): T = {
try { f } catch { case _: NullPointerException => null }
}
And I want to use it with Option like so:
val r = Option(getOrNull(someString.split("/")(0)))
I get:
Error:(25, 19) Option.type does not take parameters
What is going on, and how can I overcome this?
You might wonder what Option you are referring to.
From sbt console, use //print<tab>:
scala> Option //print
scala.Option // : Option.type
For better context:
package nooption
class Option(arg: String) // some other option on class path
object Option
object Test {
import scala.reflect.internal.util.ScalaClassLoader
def main(args: Array[String]): Unit = println {
//Option(null)
//ScalaClassLoader.originOfClass(classOf[Option])
ScalaClassLoader.originOfClass(classOf[Option$])
}
}
The class name for the companion object has a dollar at the end.
Your IDE might "go to definition."
If you started a REPL at the command line, class files in the current directory are on its class path. If you previously compiled an Option in the default or "empty" package, it will hide scala.Option.
As noted in the comments, this code does compile OK, but you really shouldn't use null in Scala unless you are interfacing with Java.
This is a better way of implementing your code:
val r = Try{ someString.split("/")(0) }.toOption
Try is widely used in Scala so this code is clear to anyone experienced with the language, so there is no need for a separate function.

Scala shapeless implicit resolution StackOverflowError with ArgonautShapeless

Im using ArgonautShapeless to define some json codecs.
When I provide the type for my codec I get StackOverflowError's but If I leave the type off it works. How can I provide the type?
My understanding of the problem is that the implicit lookup from def of[A: DecodeJson] = implicitly[DecodeJson[A]] finds my definition on the same line implicit def fooCodec: DecodeJson[Foo] and thus is recursive so breaks.
Is there some other way that will allow me to provide the type? Ideally I want to have one object in my project where I define all of the codes and they may depend on each other.
import $ivy.`com.github.alexarchambault::argonaut-shapeless_6.2:1.2.0-M4`
import argonaut._, Argonaut._
case class Foo(a: Int)
object SomeCodecs {
import ArgonautShapeless._
// this doesnt work
implicit def fooCodec: DecodeJson[Foo] = DecodeJson.of[Foo]
}
import SomeCodecs._
"""{"a":1}""".decode[Foo]
java.lang.StackOverflowError
ammonite.$sess.cmd3$SomeCodecs$.fooCodec(cmd3.sc:3)
It works if I leave the type off.
object SomeCodecs {
import ArgonautShapeless._
// this works
implicit def fooCodec = DecodeJson.of[Foo]
}
import SomeCodecs._
"""{"a":1}""".decode[Foo]
res4: Either[Either[String, (String, CursorHistory)], Foo] = Right(Foo(1))
Thanks

Robustness of pattern matching Trees with quasiquotes

I have a macro and part of that macro consists of replacing every call to a certain method with something else. To accomplish this I use a Transformer and try to match every Tree that enters the transform method against a quasiquote. When I write it like below, it seems to work.
package mypackage
object myobject {
implicit def mymethod[T](t: Option[T]): T = ???
}
object Macros {
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
def myMacro(c: Context)(expr: c.Tree): c.Tree = {
import c.universe._
val transformer = new Transformer {
private def doSomething(value: c.Tree): TermName = {
???
}
override def transform(tree: c.Tree) = tree match {
case q"mypackage.myobject.mymethod[..$_]($value)" =>
val result = doSomething(value)
q"$result"
case _ => super.transform(tree)
}
}
val transformed = transformer.transform(expr)
???
}
}
But I thought you should always use fully qualified names in macros or you could get into trouble. So I wrote it like q"_root_.mypackage.myobject.mymethod[..$_]($value)", but then it no longer matched and the calls to mymethod no longer got replaced.
I also looked at the suggestion in the scala docs to unquote symbols, but I couldn't get that to work either.
So my question is: will this code (with q"mypackage.myobject.mymethod[..$_]($value)") always replace all the calls to mymethod and never replace any other method calls? And if not, how can I make it more robust?
scala.reflect macros are non hygienic, so, theoretically, q"mypackage.myobject.mymethod[..$_]($value)" could be matched by someone else.
I'd suggest match that method with q"..$mods def $name[..$tparams](...$paramss): $tpeopt = $expr" (assuming that is definition, not declaration). You can add checks on name.
Another solution is to mark method with annotation, and remove it in macro phase.

Instantiate a class with Scala Macro or reflection

On my scala code, I want to be able to instantiate a new class. For instance, supose I have the code below:
class Foo { def foo=10 }
trait Bar { val bar=20 }
Ideally, I want to be able to do something like:
def newInstance[A <: Foo] = { new A with Bar }
newInstance[Foo]
But, of course this doesn't work. I tried to use reflection to instantiate a class, but it seems that I'm only able to instantiate a new class (and not mix-in with a trait). I think it would be possible to make this work using Macros, but I'm not sure even where to start.
What I'm trying to do is like the following Ruby code:
class SomeClass
def create
self.class.new
end
end
class Other < SomeClass
end
Other.new.create # <- this returns a new Other instance
Is it possible?
With a macro:
import scala.language.experimental.macros
import scala.reflect.macros.Context
object MacroExample {
def newInstance[A <: Foo]: A with Bar = macro newInstance_impl[A]
def newInstance_impl[A <: Foo](c: Context)(implicit A: c.WeakTypeTag[A]) = {
import c.universe._
c.Expr[A with Bar](q"new $A with Bar")
}
}
This will work as expected, and will fail at compile time if you try to instantiate a class that doesn't have a no-argument constructor.
I've used quasiquotes here for the sake of clarity, but you could build the tree manually with a little more work. There's not really any good reason to, though, now that quasiquotes are available as a plugin for Scala 2.10.