final class ContactInfo extends StaticAnnotation{}
case class Person(val id: String,
val firstName: String,
val lastName: String,
#ContactInfo val phoneNumbers: Seq[String],
#ContactInfo val email: String)
def getContactInfoFields[T: TypeTag]: Seq[String] = {
???
}
Expected output getContactInfoFields[Person] = ("phoneNumbers", "email")
Riffing off the answer to a similar question on SO I've tried
def getContactInfoFields[T: TypeTag]: Seq[String] = {
val fields = typeOf[T].members.collect{ case s: TermSymbol => s }.
filter(s => s.isVal || s.isVar)
fields.filter(_.annotations.exists(_.isInstanceOf[ContactInfo]))
.map(x=>x.name.toString).toSeq
}
However in practice this is returning an empty sequence. What am I missing?
You could represent this information at the type level.
sealed trait ContactInfo
case class PhoneNumbers(numbers: Seq[String]) extends ContactInfo
case class Email(email: String) extends ContactInfo
case class Person(id: String, firstName: String, lastName: String, phoneNumbers: PhoneNumbers, email: Email)
def contactInfo[T: TypeTag] = typeOf[T].members.filter(!_.isMethod).map(_.typeSignature).collect {
case t if t <:< typeOf[ContactInfo] => t.typeSymbol.name.toString
}
Calling contactInfo[Person] returns Iterable[String] = List(Email, PhoneNumbers)
Thank you everyone for all your help! I've managed to come up with a working solution. As it turns out I was trying to compare JavaUniverse.reflection.Types to Scala Universe reflections types which is why the filter statement was failing. The below function returns as expected
def listContactInfoFields(symbol: TypeSymbol): Seq[String] = {
val terms = symbol.asClass.primaryConstructor.typeSignature.paramLists.head
val annotatedFields = terms.filter(_.annotations.exists(_.tree.tpe =:= typeOf[ContactInfo]))
annotatedFields.map(_.name.toString)
}
Related
While domain modeling how can I ensure that two types — User and Place don't interchange their name fields, which are of type String.
trait User {
val firstName: String
val lastName: String
}
object User {
final class Live(val firstName: String,val lastName: String) extends User
def apply(firstName: String, lastName: String): User = new Live(firstName, lastName)
}
trait Place {
val name: String
}
object Place {
final class Live(val name: String) extends Place
def apply(name: String): Place = new Live(name)
}
val a = User("Tushar", "Mathur")
val b = Place("Mumbai")
val c = Place(a.firstName)
// How do I disable this ^
This is supported in Scala as in the example below. There are some libraries that can reduce boilerplate but I'm just showing the simplest option:
// define more restrictive String-like types. AnyVal construction can be free from
// overhead with some caveats.
case class UserName(name: String) extends AnyVal
case class PlaceName(name: String) extends AnyVal
// define your classes (I've changed them a bit for brevity):
case class User(name: UserName)
case class Place(name: PlaceName)
// implicits for convenience of construction:
implicit val strToUserName = UserName(_)
implicit val strToPlaceName = PlaceName(_)
// test it out:
scala> val u = User("user")
u: User = User(UserName(user))
scala> val p = Place("place")
p: Place = Place(PlaceName(place))
// as expected you CAN'T do this:
scala> User(p.name)
<console>:17: error: type mismatch;
found : PlaceName
required: UserName
User(p.name)
^
// comparison test:
scala> p.name == u.name
<console>:16: warning: comparing case class values of types PlaceName and UserName using `==' will always yield false
p.name == u.name
^
res3: Boolean = false
// you can still get at the string value:
scala> p.name
res6: PlaceName = PlaceName(place)
scala> p.name.name
res5: String = place
In the project that I am working on, there is some code that is essentially as follows:
sealed trait Character {
def tags: Seq[String]
def life: Int
// other defs
}
object Character {
def addTag[T <: Character](character: T, tag: String): T = {
val newTags = character.tags :+ tag
// character.copy(tags = newTags) // this doesn't compile
character match {
case c: Person => c.copy(tags = newTags).asInstanceOf[T]
case c: Beast => c.copy(tags = newTags).asInstanceOf[T]
// ten more cases to match each subclass
......
case _ => character
}
}
}
case class Person(title: String,
firstName: String,
lastName: String,
tags: Seq[String],
life: Int,
weapon: String
) extends Character
case class Beast(name: String,
tags: Seq[String],
life: Int,
weight: Int
) extends Character
// ten other case classes that extends Character
......
The code works, but the addTag method doesn't look very pretty for two reasons: first, it uses asInstanceOf; second, it has many lines of case c: ...... each of which are almost the same.
Is there a way to make the code better?
Since the copy method is specific to each case class (takes different parameters) it can't be used from a superclass. What you could do is:
sealed trait Character {
def tags: Seq[String]
def life: Int
// other defs
}
trait Taggable[T <: Character] {
def addTags(t: T, newTags: Seq[String]): T
}
object Character {
def addTag[T <: Character: Taggable](character: T, tag: String): T = {
val newTags = character.tags :+ tag
implicitly[Taggable[T]].addTags(character, newTags)
}
}
case class Person(title: String,
firstName: String,
lastName: String,
tags: Seq[String],
life: Int,
weapon: String
) extends Character
object Person {
implicit val taggable: Taggable[Person] = new Taggable[Person] {
override def addTags(t: Person, newTags: Seq[String]): Person = t.copy(tags = newTags)
}
}
case class Beast(name: String,
tags: Seq[String],
life: Int,
weight: Int
) extends Character
Character.addTag(Person("", "", "", Seq(), 1, ""), "")
// Character.addTag(Beast("", Seq(), 1, 1) // needs implicit as well
This uses the Taggable typeclass that must be implemented by every subclass.
I would like to apply some functions to fields in a case class. A field that has to be "transformed" is annotated using Annot.
I am trying to use Everywhere and a Poly1.
At the moment I am able to apply the transformation function to all the fields but I am not able to "pass" the information about the annotated fields.
import shapeless._
object Test {
case class Annot(
fieldType: String
) extends scala.annotation.StaticAnnotation
case class Address(
street: String,
isCapital: Boolean
)
case class Person(
#Annot("TypeA") lastName: String,
firstName: String,
isTall: Boolean,
address: Address
)
def transformationMethod(element: String, fieldType: String): String = ???
object Transformer extends Poly1 {
implicit def someStringCase: Case.Aux[String, String] = at {
case e => {
transformationMethod(e, "Name")
}
}
//other methods ..
}
def traverseAndModify[T](
expr: T
)(implicit e: Everywhere[Transformer.type, T] { type Result = T }): T = e(expr)
//Usage
val person = Person("LASTName", "FIRSTName", true, Address("My home address", true))
val modified : Person = traverseAndModify[Person](person)
}
I am wondering how to modify "everywhere" to also pass annotations to the poly function.
So I would like something similar to this in the Poly
object AnnotatedTransformer extends Poly1 {
implicit def someStringCase: Case.Aux[(String, Some[Annot]), String] = at {
case (e, Some(Annot(fieldType))) => {
transformationMethod(e, fieldType)
}
}
}
I have a case class:
case class EvaluateAddress(addressFormat: String,
screeningAddressType: String,
value: Option[String]) {
}
This was working fine until I have a new use case where "value" parameter can be a class Object instead of String.
My initial implementation to handle this use case:
case class EvaluateAddress(addressFormat: String,
screeningAddressType: String,
addressId: Option[String],
addressValue: Option[MailingAddress]) {
#JsonProperty("value")
def setAddressId(addressId: String): Unit = {
val this.`addressId` = Option(addressId)
}
def this(addressFormat: String, screeningAddressType: String, addressId: String) = {
this(addressFormat, screeningAddressType, Option(addressId), None)
}
def this(addressFormat: String, screeningAddressType: String, address: MailingAddress) = {
this(addressFormat, screeningAddressType, None, Option(address))
}
}
but I don't feel this is a good approach and it might create some problem in future.
What are the different ways I can accomplish the same?
Edit: Is there a way I can create a class containing three parameters: ** addressFormat, screeningAddressType, value** and handle both the use cases?
You do not need to provide auxilliary constructors here and neither the setter. You could simply use the copy method provided by the case class.
For example:
case class MailingAddress(email:String)
case class EvaluateAddress(addressFormat: String,
screeningAddressType: String,
addressId: Option[String],
addressValue: Option[MailingAddress])
scala> val y = EvaluateAddress("abc", "abc", None, None)
y: EvaluateAddress = EvaluateAddress(abc,abc,None,None)
scala> y.copy(addressId = Some("addressId"))
res0: EvaluateAddress = EvaluateAddress(abc,abc,Some(addressId),None)
You can have a default value for fields in a case class.
So you can have the Optional fields default to None :
case class EvaluateAddress(addressFormat: String,
screeningAddressType: String,
addressId: Option[String] = None,
addressValue: Option[MailingAddress] = None)
Then when you create a new instance of EvaluateAddress, you can choose to pass a value for either of addressId, or addressValue or both ..or nothing at all.
I'm stuck trying to define mapped tables using traits in slick 3.1.0. Since there is nothing mentioned in the official docs, I'm not even sure whether this is possible or not. Here is what I have so far:
Table definition:
class PersonTable(tag: Tag) extends Table[PersonModel](tag, "person") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def firstName = column[String]("first_name", O.Length(PersonDb.FirstNameColumnLength))
def lastName = column[String]("last_name", O.Length(PersonDb.LastNameColumnLength))
def * = (id.?, firstName, lastName) <> (PersonModelImpl.tupled, PersonModelImpl.unapply _)
}
PersonModel:
trait PersonModel {
def id: Option[Int]
def firstName: String
def lastName: String
}
PersonModelImpl:
case class PersonModelImpl(
override val id: Option[Int],
override val firstName: String,
override val lastName: String)
extends PersonModel
Compiling the code above causes an error:
Compilation error[type mismatch;
found : slick.lifted.MappedProjection[models.PersonModelImpl,(Option[Int], String, String]
required: slick.lifted.ProvenShape[models.PersonModel]]
However changing ...extends Table[PersonModel]... to ...extends Table[PersonModelImpl]... in the table definition works flawlessly.
So basically my question is:
Is it possible to use traits as TableElementType in mapped tables?
If yes, what am I doing wrong?
Answers:
Yes, but it requires having the correct projection function (*).
You have the wrong type on *. In order for the implicit to resolve to the right MappedProjection, the types have to match exactly.
You should be able to resolve both by doing:
def * = {
val applyAsPersonModel: (Option[Int], String, String) => PersonModel =
(PersonModelImpl.apply _)
val unapplyAsPersonModel: PersonModel => Option[(Option[Int], String, String)] =
{
// TODO: Handle any other subclasses of PersonModel you expect.
case personImpl: PersonModelImpl => PersonModelImpl.unapply(personImpl)
}
(id.?, firstName, lastName) <> (applyAsPersonModel.tupled, unapplyAsPersonModel)
}
Note the TODO. You'll get an exception if you try to insert any non-PersonModelImpl instances unless you add additional case statements to that partial function. Alternatively, you can just create a new PersonModelImpl instance to pass to unapply:
val unapplyAsPersonModel: PersonModel => Option[(Option[Int], String, String)] =
{ person: PersonModel =>
PersonModelImpl.unapply(
PersonModelImpl(person.id, person.firstName, person.lastName)
)
}