Matching against subclasses in macro - scala

I need to convert a string value into an actual type, so i decided to try a macro-way to do this. I have a bunch of data types:
sealed abstract class Tag(val name: String)
case object Case1 extends Tag("case1")
case object Case2 extends Tag("case2")
case object Case3 extends Tag("case3")
etc...
I want to write a simple resolver:
val tag: Tag = TagResolver.fromString("case2")
This line should return Case2 respectively. I manager to do the following:
def typeFromString(c: Context)(name: c.Expr[String]): c.Expr[Tag] = {
import c.universe._
val tag = typeTag[Tag]
val accSymb = tag.tpe.typeSymbol.asClass
val subclasses = accSymb.knownDirectSubclasses // all my cases
subclasses.map { sub =>
val name = sub.typeSignature.member(newTermName("name")).asMethod // name field
???
}
}
But how can i match name: c.Expr[String] against value of name field and if matched return the appropriate tag?

I don't think there's reliable way of doing this, because knownDirectSubclasses can refer to classes that haven't been compiled yet, so we can't evaluate them.
If you can put these values as annotations on the classes, then these annotations can be read even when classes are being compiled in the current compilation run (via the Symbol.annotations API). Please note, however, that knownDirectSubclasses has known issues: https://issues.scala-lang.org/browse/SI-7046.

Related

How to list annotations (custom java ones and others) on field members of Scala case class

So I'm trying to list fields with specific annotation in a Scala case class and I'm not able to get it working... Let's see come code right away
The case class (it's a simplified version of it, mine extends another class and is also nested in my test class where I use it for unit testing only):
case class Foo(#Unique var str: String) {}
The custom Java annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.PARAMETER})
public #interface Unique {}
And my class (simplified again) where I'm trying to do some stuffs with fields marked as unique
class SomeClass[T] (implicit typeTag: TypeTag[T]) {
val fields: Iterable[universe.TermSymbol] = typeOf(typeTag).members.collect { case s: TermSymbol => s }.
filter(s => s.isVal || s.isVar)
val list = fields.flatMap(f => f.annotations.find(_.tpe =:= TypeOf[Unique]).((f, _))).toList
}
But the val list in the last peace of code is always empty... fields has str listed in but without the annotation.
What am I missing?
The code listing the annotations is from the following answer:
How to list all fields with a custom annotation using Scala's reflection at runtime?
Seems the reference post is Scala 2.10 is old and is not compatible with the newest Scala version.
There is an example for how to get the specify annotation by type.
def listProperties[T: TypeTag]: List[universe.Annotation] = {
typeOf[T].typeSymbol.asClass
.asClass
.primaryConstructor
.typeSignature
.paramLists.flatten.flatMap(_.annotations)
}
val annotations = listProperties[Foo].filter(_.tree.tpe =:= typeOf[Unique])
println(annotations)
and there is way to get the annotation's field value:
case class Foo(#Unique(field = "bar") val str: String) {}
import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
val tb = currentMirror.mkToolBox()
val result = tb.eval(tb.untypecheck(head.tree)).asInstanceOf[Unique]
and need to call out your annotation class is implemented by using Java style, In Scala maybe you want to use StaticAnnotation for creating Annotation, like:
class Unique extends StaticAnnotation

Scala case class conversion

Is there a way to convert one case class to another when they have the same fields and inherit from the same trait, without providing a converter function (that would simply do the one to one field mapping)?
For example:
trait UberSomething {
val name: String
}
// these may be located in different files
case class Something(name: String) extends UberSomething
case class SomethingOther(name: String) extends UberSomething
val s = Something("wtv")
//s.asInstanceOf[SomethingOther] FAILS
First of all never define trait members as val if they are meant to be implemented at a later point.
trait UberSomething {
def name: String
}
// these maybe in different files
case class Something(name: String) extends UberSomething
case class SomethingOther(name: String) extends UberSomething
import shapeless._, ops.hlist.Align
Another approach I've seen somewhere on Stackoverflow before, so apologies for stealing street cred, is to use Align such that order of the fields wouldn't matter.
class Convert[Target] {
def apply[Source, HLS <: HList, HLT <: HList](s: Source)(implicit
// Convert the Source to an HList type
// include field names, e.g "labelled"
// Shapeless "generates" this using an implicit macro
// it looks at our type, extracts a list of (Name, Type) pairs
genS: LabelledGeneric.Aux[Source, HLS],
// Convert the Target o an HList type
// include field names, e.g "labelled"
// So again we have a (Name, Type) list of pairs this time for Target
genT: LabelledGeneric.Aux[Target, HLT],
// Use an implicit align to make sure the two HLists
// contain the same set of (Name, Type) pairs in arbitrary order.
align: Align[HLS, HLT]
) = genT from align(genS to s)
}
// Small trick to guarantee conversion only requires
// a single type argument, otherwise we'd have to put something
// in place for HLS and HLT, which are meant to be path dependant
// and "calculated" by the LabelledGeneric.Repr macro so it wouldn't work as it breaches the "Aux pattern", which exposes a type member materialized by a macro in this case.
// HLT and HLS come from within genS.Repr and genT.Repr.
def convert[T] = new Convert[T]
This is a bit better as the HList params are nicely masked as part of apply so you don't trip yourself up.
val sample = Something("bla")
convert[SomethingOther](sample) // SomethingOther("bla")
Let's review this line: genT from align(genS to s).
First genS to s converts the Source instance to a LabelledGeneric, e.g an HList with field info.
Align aligns the types and fields of the created HList for the Source type to match the Target type.
genT from .. allows us to create an instance of Target from an HList granted the compiler can "prove" the fields and types are "all there", which is something we already have with Align.
You can do that using implicit conversions, eg:
trait UberSomething {
val name: String
}
case class Something(name: String) extends UberSomething
case class SomethingOther(name: String) extends UberSomething
object Something {
implicit def somethingToSomethingOther(s:Something):SomethingOther = SomethingOther(s.name)
}
object SomethingOther {
implicit def somethingOtherToSomething(s:SomethingOther):Something = Something(s.name)
}
val s = Something("wtv")
val so:SomethingOther = s

How to create my own custom converts class

I have a very generic message object that I get back from a queue like:
case class Message(key: String, properties: Map[String, String])
I then have a bunch of very specific classes that represent a message, and I use properties.get("type") to determine which particular message it is:
sealed trait BaseMessage
case class LoginMessage(userId: Int, ....) extends BaseMessage
case class RegisterMessage(email: String, firstName: String, ....) extends BaseMessage
Now in my code I have to convert from a generic Message to a particular message in many places, and I want to create this in a single place like:
Currently I am doing something like:
val m = Message(....)
val myMessage = m.properties.get("type") match {
case Some("login") => LoginMessage(m.properties("userID"), ...)
case ...
}
What options do I have in making this less cumbersome in scala?
I don't know all your context here, but I can suggest using implicit conversions if you don't want to bring another library in your project. Anyway, implicit conversions can help you separate a lot the implementation or override it "on-the-fly" as needed.
We can start by defining a MessageConverter trait that is actually a function:
/**
* Try[T] here is useful to track deserialization errors. If you don't need it you can use Option[T] instead.
*/
trait MessageConverter[T <: BaseMessage] extends (Message => Try[T])
Now define an object that holds both the implementations and also enables a nice #as[T] method on Message instances:
object MessageConverters {
/**
* Useful to perform conversions such as:
* {{{
* import MessageConverters._
*
* message.as[LoginMessage]
* message.as[RegisterMessage]
* }}}
*/
implicit class MessageConv(val message: Message) extends AnyVal {
def as[T <: BaseMessage : MessageConverter]: Try[T] =
implicitly[MessageConverter[T]].apply(message)
}
// Define below message converters for each particular type
implicit val loginMessageConverter = new MessageConverter[LoginMessage] {
override def apply(message: Message): Try[LoginMessage] = {
// Parse the properties and build the instance here or fail if you can't.
}
}
}
That's it! It may not be the best solution as implicits bring complexity and they make code harder to follow. However, if you follow a well-defined structure for storing these implicit values and be careful how you pass them around, then you shouldn't have any issues.
You can convert the properties map to Json and read it as a case class. Assuming that the keys to the map have the same name as your case class fields you can write a formatter using playjson:
object LoginMessage {
implicit val fmtLoginMessage = Json.format[LoginMessage]
}
If the fields don't have the same name you will have to specify the reads object manually. Your code to convert it into a case class would be something like:
object BaseMessageFactory {
def getMessage(msg: Message): Option[BaseMessage] = {
val propertiesJson = Json.toJson(msg.properties)
msg.properties.get("type").map {
case "login" => propertiesJson.as[LoginMessage]
...
case _ => //Some error
}
}
}
The signature may differ depending on how you want to deal with error handling.

How to add reusable field to Scala Enumeration?

I would like to extend Scala's implementation of Enumeration with a custom field, say label. That new field should be accessible via the values of that enumeration. Furthermore, that custom field should be part of various implementations of Enumeration.
I am aware of the following questions at Stackoverflow:
How to add a method to Enumeration in Scala?
How do I create an enum in scala that has an extra field
Overriding Scala Enumeration Value
Scala doesn't have enums - what to use instead of an enum
However, none of them solves my issues:
The first issue is that I am able to add a custom field. However, I cannot access that additional field via the Values returned by Enumeration.values. The following code works and prints 2nd enumeration value:
object MyEnum extends Enumeration {
type MyEnum = MyVal
val VALUE_ONE = MyVal()
val VALUE_TWO = MyVal(Some("2nd enumeration value"))
val VALUE_THREE = MyVal(Some("3rd value"))
case class MyVal(label: Option[String] = None) extends Val(nextId)
}
import MyEnum._
println(VALUE_TWO.label.get)
Note that I access the label via one of the values. The following code does not work:
for (value <- MyEnum.values) println(value.label)
The error message is as follows: error: value label is not a member of MyEnum.Value
Obviously, instead of MyEnum.MyVal, MyEnum.Val is used. The latter does not define label, while my custom value would provide field label.
The second issue is that it seems to be possible to introduce a custom Value and Val, respectively, in the context of an Enumeration only. Thus, as far as I know, it is not possible to use such a field across different enums. At least, the following code does not compile:
case class MyVal(label: Option[String] = None) extends Enumeration.Val(nextId)
object MyEnum extends Enumeration {
type MyEnum = MyVal
val VALUE_ONE = MyVal()
val VALUE_TWO = MyVal(Some("2nd enumeration value"))
}
object MySecondEnum extends Enumeration {
type MySecondEnum = MyVal
val VALUE_ONE = MyVal()
val VALUE_TWO = MyVal(Some("2nd enumeration value"))
}
Due to the fact that class Val is protected, case class MyVal cannot access Val -- MyVal is not defined in the context of an enumeration.
Any idea how to solve the above issues?
The first issue is addressed by a recent question, my answer to which got no love.
For that use case, I would write a custom widgets method with the useful type, but my linked answer, which just introduces an implicit conversion, seems pretty handy. I don't know why it's not the canonical solution.
For the second issue, your derived MyVal should just implement a trait.
Sample:
scala> trait Labelled { def label: Option[String] }
defined trait Labelled
scala> object A extends Enumeration { case class AA(label: Option[String]) extends Val with Labelled ; val X = AA(Some("one")) }
defined object A
scala> object B extends Enumeration { case class BB(label: Option[String]) extends Val with Labelled ; val Y = BB(None) }
defined object B
scala> val labels = List(A.X, B.Y)
labels: List[Enumeration#Val with Product with Labelled] = List(X, Y)
scala> labels map (_.label)
res0: List[Option[String]] = List(Some(one), None)

Custom Scala enum, most elegant version searched

For a project of mine I have implemented a Enum based upon
trait Enum[A] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency
case object GBP extends Currency
}
from Case objects vs Enumerations in Scala. I worked quite nice, till I run into the following problem. Case objects seem to be lazy and if I use Currency.value I might actually get an empty List. It would have been possible to make a call against all Enum Values on startup so that the value list would be populated, but that would be kind of defeating the point.
So I ventured into the dark and unknown places of scala reflection and came up with this solution, based upon the following SO answers. Can I get a compile-time list of all of the case objects which derive from a sealed parent in Scala?
and How can I get the actual object referred to by Scala 2.10 reflection?
import scala.reflect.runtime.universe._
abstract class Enum[A: TypeTag] {
trait Value
private def sealedDescendants: Option[Set[Symbol]] = {
val symbol = typeOf[A].typeSymbol
val internal = symbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol]
if (internal.isSealed)
Some(internal.sealedDescendants.map(_.asInstanceOf[Symbol]) - symbol)
else None
}
def values = (sealedDescendants getOrElse Set.empty).map(
symbol => symbol.owner.typeSignature.member(symbol.name.toTermName)).map(
module => reflect.runtime.currentMirror.reflectModule(module.asModule).instance).map(
obj => obj.asInstanceOf[A]
)
}
The amazing part of this is that it actually works, but it is ugly as hell and I would be interested if it would be possible to make this simpler and more elegant and to get rid of the asInstanceOf calls.
Here is a simple macro based implementation:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
abstract class Enum[E] {
def values: Seq[E] = macro Enum.caseObjectsSeqImpl[E]
}
object Enum {
def caseObjectsSeqImpl[A: c.WeakTypeTag](c: blackbox.Context) = {
import c.universe._
val typeSymbol = weakTypeOf[A].typeSymbol.asClass
require(typeSymbol.isSealed)
val subclasses = typeSymbol.knownDirectSubclasses
.filter(_.asClass.isCaseClass)
.map(s => Ident(s.companion))
.toList
val seqTSymbol = weakTypeOf[Seq[A]].typeSymbol.companion
c.Expr(Apply(Ident(seqTSymbol), subclasses))
}
}
With this you could then write:
sealed trait Currency
object Currency extends Enum[Currency] {
case object USD extends Currency
case object EUR extends Currency
}
so then
Currency.values == Seq(Currency.USD, Currency.EUR)
Since it's a macro, the Seq(Currency.USD, Currency.EUR) is generated at compile time, rather than runtime. Note, though, that since it's a macro, the definition of the class Enum must be in a separate project from where it is used (i.e. the concrete subclasses of Enum like Currency). This is a relatively simple implementation; you could do more complicated things like traverse multilevel class hierarchies to find more case objects at the cost of greater complexity, but hopefully this will get you started.
A late answer, but anyways...
As wallnuss said, knownDirectSubclasses is unreliable as of writing and has been for quite some time.
I created a small lib called Enumeratum (https://github.com/lloydmeta/enumeratum) that allows you to use case objects as enums in a similar way, but doesn't use knownDirectSubclasses and instead looks at the body that encloses the method call to find subclasses. It has proved to be reliable thus far.
The article "“You don’t need a macro” Except when you do" by Max Afonov
maxaf describes a nice way to use macro for defining enums.
The end-result of that implementation is visible in github.com/maxaf/numerato
Simply create a plain class, annotate it with #enum, and use the familiar val ... = Value declaration to define a few enum values.
The #enum annotation invokes a macro, which will:
Replace your Status class with a sealed Status class suitable for acting as a base type for enum values. Specifically, it'll grow a (val index: Int, val name: String) constructor. These parameters will be supplied by the macro, so you don't have to worry about it.
Generate a Status companion object, which will contain most of the pieces that now make Status an enumeration. This includes a values: List[Status], plus lookup methods.
Give the above Status enum, here's what the generated code looks like:
scala> #enum(debug = true) class Status {
| val Enabled, Disabled = Value
| }
{
sealed abstract class Status(val index: Int, val name: String)(implicit sealant: Status.Sealant);
object Status {
#scala.annotation.implicitNotFound(msg = "Enum types annotated with ".+("#enum can not be extended directly. To add another value to the enum, ").+("please adjust your `def ... = Value` declaration.")) sealed abstract protected class Sealant;
implicit protected object Sealant extends Sealant;
case object Enabled extends Status(0, "Enabled") with scala.Product with scala.Serializable;
case object Disabled extends Status(1, "Disabled") with scala.Product with scala.Serializable;
val values: List[Status] = List(Enabled, Disabled);
val fromIndex: _root_.scala.Function1[Int, Status] = Map(Enabled.index.->(Enabled), Disabled.index.->(Disabled));
val fromName: _root_.scala.Function1[String, Status] = Map(Enabled.name.->(Enabled), Disabled.name.->(Disabled));
def switch[A](pf: PartialFunction[Status, A]): _root_.scala.Function1[Status, A] = macro numerato.SwitchMacros.switch_impl[Status, A]
};
()
}
defined class Status
defined object Status