I've defined a simple generic macro:
object MyMacro {
def readWrite[T](readParse: String => T, label: String, format: T => String): Unit = macro readWriteImpl[T]
def readWriteImpl[T: c.WeakTypeTag](c: Context)(readParse: c.Expr[String => T], label: c.Expr[String], format: c.Expr[T => String]): c.Tree = {
import c.universe._
q"""
def read[WIRE](path: Path, reader: Transceiver[WIRE], isMapKey: Boolean = false): T =
reader.readString(path) match {
case null => null.asInstanceOf[T]
case s => Try( $readParse(s) ) match {
case Success(d) => d
case Failure(u) => throw new ReadMalformedError(path, "Failed to parse "+$label+" from input '"+s+"'", List.empty[String], u)
}
}
def write[WIRE](t: T, writer: Transceiver[WIRE], out: Builder[Any, WIRE]): Unit =
t match {
case null => writer.writeNull(out)
case _ => writer.writeString($format(t), out)
}
"""
}
}
In a separate compilation unit I use it like this:
object DurationTypeAdapterFactory extends TypeAdapter.=:=[Duration] {
MyMacro.readWrite[Duration]((s: String) => Duration.parse(s), "Duration", (t: Duration) => t.toString)
}
When built, the compiler complains it doesn't know about T:
[error] /Users/me/git/ScalaJack/core/src/main/scala/co.blocke.scalajack/typeadapter/TimePrimitives.scala:13:30: not found: type T
[error] MyMacro.readWrite[Duration]((s: String) => Duration.parse(s), "Duration", (t: Duration) => t.toString)
[error]
It doesn't like the 'T' references in my quasiquote, which I kinda understand. How can I represent the T paraeter passed into readWriteImpl inside the quasiquote so that it unpacks properly?
Use the tag to inspect the type or compare it to other types using =:=, or use it in the expansion.
For example,
scala 2.13.0-M5> def fImpl[A: c.WeakTypeTag](c: Context)(a: c.Expr[A]) = { import c._, universe._
| q"null.asInstanceOf[${implicitly[c.WeakTypeTag[A]].tpe}]" }
fImpl: [A](c: scala.reflect.macros.blackbox.Context)(a: c.Expr[A])(implicit evidence$1: c.WeakTypeTag[A])c.universe.Tree
scala 2.13.0-M5> import language.experimental.macros ; def f[A](a: A): A = macro fImpl[A]
import language.experimental.macros
defined term macro f: [A](a: A)A
scala 2.13.0-M5> f(42)
res2: Int = 0
scala 2.13.0-M5> f("")
res3: String = null
Related
I have the following function, that do recursion:
#tailrec
private def pool[F[_]: Monad, A]
: Consumer[String, String] => (Vector[KkConsumerRecord] => F[A]) => IO[Unit]
= consumer => cb => {
val records: ConsumerRecords[String, String] = consumer.poll(Long.MaxValue)
val converted = records.iterator().asScala.map(rec => {
KkConsumerRecord(rec.key(), rec.value(), rec.offset(), rec.partition(), rec.topic())
})
val vec = converted.foldLeft(Vector.empty[KkConsumerRecord]) { (b, a) =>
a +: b
}
cb(vec)
pool(consumer)(cb)
}
The compiler complains:
[error] /home/developer/Desktop/microservices/bary/kafka-api/src/main/scala/io/khinkali/Consumer/KkConsumer.scala:57:10: type mismatch;
[error] found : org.apache.kafka.clients.consumer.Consumer[String,String]
[error] required: cats.Monad[?]
[error] pool(consumer)(cb)
[error] ^
[error] two errors found
What am I doing wrong?
The following code compiles:
import cats.Monad
import cats.effect.IO
import org.apache.kafka.clients.consumer.{Consumer, ConsumerRecords}
import scala.collection.JavaConverters._
import scala.annotation.tailrec
object App {
case class KkConsumerRecord(key: String, value: String, offset: Long, partition: Int, topic: String)
// #tailrec
private def pool[F[_]: Monad, A]
: Consumer[String, String] => (Vector[KkConsumerRecord] => F[A]) => IO[Unit]
= consumer => cb => {
val records: ConsumerRecords[String, String] = consumer.poll(Long.MaxValue)
val converted = records.iterator().asScala.map(rec => {
KkConsumerRecord(rec.key(), rec.value(), rec.offset(), rec.partition(), rec.topic())
})
val vec = converted.foldLeft(Vector.empty[KkConsumerRecord]) { (b, a) =>
a +: b
}
cb(vec)
pool.apply(consumer)(cb)
}
}
def pool[F[_]: Monad, A] means def pool[F[_], A](implicit monad: Monad[F]) so compiler mistreated consumer as implicit parameter.
tailrec annotation is removed since pool is not tail recursive (the last operation is constructing lambda, I guess it's called tail recursion modulo cons).
If you want to make it tail-recursive you can rewrite it as
#tailrec
private def pool[F[_]: Monad, A](consumer: Consumer[String, String])(cb: Vector[KkConsumerRecord] => F[A]): IO[Unit] = {
val records: ConsumerRecords[String, String] = consumer.poll(Long.MaxValue)
val converted = records.iterator().asScala.map(rec => {
KkConsumerRecord(rec.key(), rec.value(), rec.offset(), rec.partition(), rec.topic())
})
val vec = converted.foldLeft(Vector.empty[KkConsumerRecord]) { (b, a) =>
a +: b
}
cb(vec)
pool(consumer)(cb)
}
The following should show the problem:
class Container[A](val xs: List[A]) {
def foo(fun: A => A)(implicit ord: Ordering[A]): List[A] =
macro ContainerMacros.fooImpl // how to pass `xs`?
}
object ContainerMacros {
def fooImpl(c: blackbox.Context)
(fun: c.Expr[Nothing => Any])
(ord: c.Expr[Ordering[_]]): c.Expr[List[Nothing]] = {
import c.universe._
reify {
// val cont = c.prefix.splice.asInstanceOf[Container[_]]
// cont.xs.map(fun.splice).sorted(ord.splice)
???
}
}
}
That is, there seems no way to add A as a type parameter to fooImpl (if I do that, the compiler complains). So I have to remove it, but then the question is how to get the things in reify working the intended way, i.e. how to reintroduce the missing type parameter A.
Here is an attempt:
def fooImpl(c: blackbox.Context)
(fun: c.Expr[Nothing => Any])
(ord: c.Expr[Ordering[_]]): c.Expr[List[Nothing]] = {
import c.universe._
reify {
val ext = c.prefix.splice.asInstanceOf[ExtensionTest[_]]
import ext.{A1 => A}
val extC = ext .asInstanceOf[ExtensionTest[A]]
val funC = fun.splice.asInstanceOf[A => A]
val ordC = ord.splice.asInstanceOf[Ordering[A]]
val xs = extC.xs.asInstanceOf[List[A]] // computer says no
xs.map(funC).sorted(ordC)
}
}
The xs.map(funC) doesn't work with one of those legendary scalac error messages:
Error:(27, 16) type mismatch;
found : _$2 => Any where type _$2
required: Any => ?
xs.map(funC).sorted(ordC)
Answer actually thanks to #edmundnoble via Scala Gitter channel:
You can add type parameters in the impl call, thus it's possible to pass the A from Container to the macro expansion:
class Container[A](val xs: List[A]) {
def foo(fun: A => A)(implicit ord: Ordering[A]): List[A] =
macro ContainerMacros.fooImpl[A]
}
object ContainerMacros {
def fooImpl[A](c: blackbox.Context)
(fun: c.Expr[A => A])
(ord: c.Expr[Ordering[A]])
(implicit t: c.WeakTypeTag[A]): c.Expr[List[A]] = {
import c.universe._
reify {
val extC = c.prefix.splice.asInstanceOf[Container[A]]
val funC = fun.splice
val ordC = ord.splice
val xs = extC.xs
xs.map(funC).sorted(ordC)
}
}
}
I would like to do something like this:
implicit class MyString(s: String) {
def getAs[T]: T = {
T match {
case q if q == classOf[Int] => s.toInt
case q if q == classOf[Boolean] => s.toBoolean
}
}
}
This doesn't compile, of course. How do I write this so it does?
Consider this approach:
object Parse {
def parse[T](f:String => Option[T]) = f
implicit val parseInt = parse(s => Try(s.toInt).toOption)
implicit val parseLong = parse(s => Try(s.toLong).toOption)
implicit val parseDouble = parse(s => Try(s.toDouble).toOption)
implicit val parseBoolean = parse(s => Try(s.toBoolean).toOption)
}
implicit class MyString(s:String) {
def getAs[T]()(implicit run: String => Option[T]): Option[T] = run(s)
}
Usage:
def main(args: Array[String]) {
import Parse._
"true".getAs[Boolean].foreach(println)
"12345".getAs[Int].foreach(println)
}
When use classOf for type match, there maybe will have some issues, example:
scala> classOf[List[Int]] == classOf[List[String]]
res17: Boolean = true
scala> typeOf[List[Int]] =:= typeOf[List[String]]
res18: Boolean = false
classOf only store the class information not with generics type
typeOf will store full type information
TypeTags and Manifests
class MyString(s: String) {
def getAs[T](implicit tag: TypeTag[T]): T = {
tag.tpe match {
case t if t =:= typeOf[Int] => s.toInt.asInstanceOf[T]
}
}
}
scala> new MyString("123")
res2: MyString = MyString#7fca02
scala> res6.getAs[Int]
res3: Int = 123
use TypeType tag to get type info, and call getAs with type information.
This is what I have come up with so far:
import reflect.runtime.universe.TypeTag
import scala.reflection.runtime.universe._
implicit class MyString(s: String) {
def getAs[T : TypeTag]: T = {
typeOf[T] match {
case t if t =:= typeOf[Int] => s.toInt.asInstanceOf[T]
case t if t =:= typeOf[Boolean] => s.toBoolean.asInstanceOf[T]
}
}
}
Running with this in REPL:
scala> "32".getAs[Int]
res25: Int = 32
scala> "32".getAs[Boolean]
java.lang.IllegalArgumentException: For input string: "32"
at scala.collection.immutable.StringLike$class.parseBoolean(StringLike.scala:290)
at scala.collection.immutable.StringLike$class.toBoolean(StringLike.scala:260)
at scala.collection.immutable.StringOps.toBoolean(StringOps.scala:30)
at MyString.getAs(<console>:33)
... 43 elided
scala> "false".getAs[Int]
java.lang.NumberFormatException: For input string: "false"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at scala.collection.immutable.StringLike$class.toInt(StringLike.scala:272)
at scala.collection.immutable.StringOps.toInt(StringOps.scala:30)
at MyString.getAs(<console>:32)
... 43 elided
scala> "false".getAs[Boolean]
res28: Boolean = false
scala> "false".getAs[String]
scala.MatchError: String (of class scala.reflect.internal.Types$AliasNoArgsTypeRef)
at MyString.getAs(<console>:31)
... 43 elided
Better, I think, would be to return an option[T], and catch any issues such as a type T that isn't catered for, or attempting a cast that will fail. I started mucking around with scala's Try (and its .toOption method), but hit some odd errors so haven't gotten any further with that.
EDIT: just using a simple try-catch, we can get:
implicit class MyString(s: String) {
def getAs[T : TypeTag]: Option[T] = try {
typeOf[T] match {
case t if t =:= typeOf[Int] => Some(s.toInt.asInstanceOf[T])
case t if t =:= typeOf[Boolean] => Some(s.toBoolean.asInstanceOf[T])
}
} catch {
case ex: Exception => None
}
}
Resulting in:
scala> "false".getAs[String]
res30: Option[String] = None
scala> "32".getAs[Boolean]
res31: Option[Boolean] = None
scala> "32".getAs[Int]
res32: Option[Int] = Some(32)
scala> "true".getAs[Boolean]
res33: Option[Boolean] = Some(true)
scala> "true".getAs[Int]
res34: Option[Int] = None
Based on the answers given by #chengpohi and #Shadowlands, this is what I came up with.
object ImplicitsStartingWithS {
implicit class MyString(s: String) {
val str = s.trim
import reflect.runtime.universe.TypeTag
import scala.reflect.runtime.universe._
def getAs[T](implicit tag: TypeTag[T]): Option[T] = {
val value = tag.tpe match {
case t if t =:= typeOf[Int] => str.toIntStr.map(_.toInt)
case t if t =:= typeOf[Long] => str.toIntStr.map(_.toLong)
case t if t =:= typeOf[Float] => str.toNumericStr.map(_.toFloat)
case t if t =:= typeOf[Double] => str.toNumericStr.map(_.toDouble)
case _ => None
}
value.asInstanceOf[Option[T]]
}
def toDecimalStr = "^-*\\d+\\.\\d+$".r.findFirstIn(s)
def toIntStr = "^-*\\d+$".r.findFirstIn(s)
def toNumericStr = {
s.toDecimalStr match {
case Some(decimalStr) => Some(decimalStr)
case None => s.toIntStr
}
}
}
}
This avoids exception handling for faster response.
Essentially, what I would like to do is write overloaded versions of "map" for a custom class such that each version of map differs only by the type of function passed to it.
This is what I would like to do:
object Test {
case class Foo(name: String, value: Int)
implicit class FooUtils(f: Foo) {
def string() = s"${f.name}: ${f.value}"
def map(func: Int => Int) = Foo(f.name, func(f.value))
def map(func: String => String) = Foo(func(f.name), f.value)
}
def main(args: Array[String])
{
def square(n: Int): Int = n * n
def rev(s: String): String = s.reverse
val f = Foo("Test", 3)
println(f.string)
val g = f.map(rev)
val h = g.map(square)
println(h.string)
}
}
Of course, because of type erasure, this won't work. Either version of map will work alone, and they can be named differently and everything works fine. However, it is very important that a user can call the correct map function simply based on the type of the function passed to it.
In my search for how to solve this problem, I cam across TypeTags. Here is the code I came up with that I believe is close to correct, but of course doesn't quite work:
import scala.reflect.runtime.universe._
object Test {
case class Foo(name: String, value: Int)
implicit class FooUtils(f: Foo) {
def string() = s"${f.name}: ${f.value}"
def map[A: TypeTag](func: A => A) =
typeOf[A] match {
case i if i =:= typeOf[Int => Int] => f.mapI(func)
case s if s =:= typeOf[String => String] => f.mapS(func)
}
def mapI(func: Int => Int) = Foo(f.name, func(f.value))
def mapS(func: String => String) = Foo(func(f.name), f.value)
}
def main(args: Array[String])
{
def square(n: Int): Int = n * n
def rev(s: String): String = s.reverse
val f = Foo("Test", 3)
println(f.string)
val g = f.map(rev)
val h = g.map(square)
println(h.string)
}
}
When I attempt to run this code I get the following errors:
[error] /src/main/scala/Test.scala:10: type mismatch;
[error] found : A => A
[error] required: Int => Int
[error] case i if i =:= typeOf[Int => Int] => f.mapI(func)
[error] ^
[error] /src/main/scala/Test.scala:11: type mismatch;
[error] found : A => A
[error] required: String => String
[error] case s if s =:= typeOf[String => String] => f.mapS(func)
It is true that func is of type A => A, so how can I tell the compiler that I'm matching on the correct type at runtime?
Thank you very much.
In your definition of map, type A means the argument and result of the function. The type of func is then A => A. Then you basically check that, for example typeOf[A] =:= typeOf[Int => Int]. That means func would be (Int => Int) => (Int => Int), which is wrong.
One of ways of fixing this using TypeTags looks like this:
def map[T, F : TypeTag](func: F)(implicit ev: F <:< (T => T)) = {
func match {
case func0: (Int => Int) #unchecked if typeOf[F] <:< typeOf[Int => Int] => f.mapI(func0)
case func0: (String => String) #unchecked if typeOf[F] <:< typeOf[String => String] => f.mapS(func0)
}
}
You'd have to call it with an underscore though: f.map(rev _). And it may throw match errors.
It may be possible to improve this code, but I'd advise to do something better. The simplest way to overcome type erasure on overloaded method arguments is to use DummyImplicit. Just add one or several implicit DummyImplicit arguments to some of the methods:
implicit class FooUtils(f: Foo) {
def string() = s"${f.name}: ${f.value}"
def map(func: Int => Int)(implicit dummy: DummyImplicit) = Foo(f.name, func(f.value))
def map(func: String => String) = Foo(func(f.name), f.value)
}
A more general way to overcome type erasure on method arguments is to use the magnet pattern. Here is a working example of it:
sealed trait MapperMagnet {
def map(foo: Foo): Foo
}
object MapperMagnet {
implicit def forValue(func: Int => Int): MapperMagnet = new MapperMagnet {
override def map(foo: Foo): Foo = Foo(foo.name, func(foo.value))
}
implicit def forName(func: String => String): MapperMagnet = new MapperMagnet {
override def map(foo: Foo): Foo = Foo(func(foo.name), foo.value)
}
}
implicit class FooUtils(f: Foo) {
def string = s"${f.name}: ${f.value}"
// Might be simply `def map(func: MapperMagnet) = func.map(f)`
// but then it would require those pesky underscores `f.map(rev _)`
def map[T](func: T => T)(implicit magnet: (T => T) => MapperMagnet): Foo =
magnet(func).map(f)
}
This works because when you call map, the implicit magnet is resolved at compile time using full type information, so no erasure happens and no runtime type checks are needed.
I think the magnet version is cleaner, and as a bonus it doesn't use any runtime reflective calls, you can call map without underscore in the argument: f.map(rev), and also it can't throw runtime match errors.
Update:
Now that I think of it, here magnet isn't really simpler than a full typeclass, but it may show the intention a bit better. It's a less known pattern than typeclass though. Anyway, here is the same example using the typeclass pattern for completeness:
sealed trait FooMapper[F] {
def map(foo: Foo, func: F): Foo
}
object FooMapper {
implicit object ValueMapper extends FooMapper[Int => Int] {
def map(foo: Foo, func: Int => Int) = Foo(foo.name, func(foo.value))
}
implicit object NameMapper extends FooMapper[String => String] {
def map(foo: Foo, func: String => String) = Foo(func(foo.name), foo.value)
}
}
implicit class FooUtils(f: Foo) {
def string = s"${f.name}: ${f.value}"
def map[T](func: T => T)(implicit mapper: FooMapper[T => T]): Foo =
mapper.map(f, func)
}
Generic case class
case class GroupResult[T](
group: String,
reduction: Seq[T]
)
Macro method
def foo[T] = macro fooImpl[T]
def fooImpl[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val tpe = weakTypeOf[T]
tpe.declarations.collect {
case m: MethodSymbol if m.isCaseAccessor => println(m.returnType)
}
c.literalUnit
}
When I invoke foo[GroupResult[Int]]
The output is
String
Seq[T]
T is not applied ? How can I get the applied Seq[Int] ?
You can use typeSignatureIn to get the type signature of a method given GroupResult[Int]:
import scala.language.experimental.macros
import scala.reflect.macros.Context
case class GroupResult[T](group: String, reduction: Seq[T])
def foo[T] = macro fooImpl[T]
def fooImpl[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val tpe = weakTypeOf[T]
tpe.declarations.collect {
case m: MethodSymbol if m.isCaseAccessor => println(m.typeSignatureIn(tpe))
}
c.literalUnit
}
And then:
scala> foo[GroupResult[Int]]
=> String
=> Seq[Int]
So we're closer, but now we're getting the "types" of the accessors, not their return types. If we want the return types we can use the NullaryMethodType extractor:
def foo[T] = macro fooImpl[T]
def fooImpl[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val tpe = weakTypeOf[T]
tpe.declarations.collect {
case m: MethodSymbol if m.isCaseAccessor => m.typeSignatureIn(tpe) match {
case NullaryMethodType(returnType) => println(returnType)
}
}
c.literalUnit
}
And then:
scala> foo[GroupResult[Int]]
String
Seq[Int]
And we're done.