Scala trait implementation - scala

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.

Related

Scala: How to define a method that returns a instance of subclass

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.

Scala. Case class copy from trait reference

Hi I'm trying to solve a problem in kind of "elegant" and type safe way but I can't find the best...
Let's say I have this trait
trait Event {
def deviceId: String
def userId: String
def eventDateTime: DateTime
def payload: Option[Payload]
}
trait Payload
And following case classes (there could be more)
case class AEvent (deviceId: String, userId: String, eventDateTime: DateTime, payload: Option[APayload]) extends Event
case class APayload (content: String)
case class BEvent (deviceId: String, userId: String, eventDateTime: DateTime, payload: Option[BPayload]) extends Event
case class BPayload (size: Int, name: String)
I would like to use case class copy method directly from the trait without casting to AEvent or BEvent...
As I'm having a reference to the trait, best solution I figured out is to create a method like this:
def copy[T <: Event](event: T)(deviceId: String = event.deviceId,
userId: String = event.userId,
eventDateTime: DateTime = event.eventDateTime,
payload: Option[Payload] = event.payload) T = {
val res = event match {
case x: AEvent => AEvent(deviceId, userId, eventDateTime, payload.asInstanceOf[APayload])
case x: BEvent => BEvent(deviceId, userId, eventDateTime, payload.asInstanceOf[BPayload])
}
res.asInstanceOf[T]
}
What I don't like is that Payload type is casted in runtime...
How can I have type check during compile time?
Thanks in advance
What about
case class Event[P <: Payload](deviceId: String, userId: String, eventDateTime: DateTime, payload: Option[P])
and using Event[APayload] instead of AEvent?

Get field names from Scala case class with specific annotation

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)
}

Implicitly convert parameter Option[T] to T in a case class

I had a case class with a option parameter, let's say:
case class Student(id: Option[Int], name: String)
To get a Student instance, not only I could use Student(Some(1), "anderson"), I also want this form to be a valid way Student(2,"Sarah")
I guess I have to create a Int => Option[Int] and put it somewhere. So what's the best way to do so?
Update
As mentioned in the comment, override apply method will block calling it by Student.apply _
It might be easier to just make an apply method in a companion object.
case class Student(id: Option[Int], name: String)
object Student {
def apply(id: Int, name: String): Student = {
Student(Some(id), name)
}
}
An alternative solution using implicit conversions:
implicit def intToOption(x: Int) = Some(x)
case class Student(id: Option[Int], name: String)
scala> Student(1,"Nu")
res1: Student = Student(Some(1),Nu)

Transform one case class into another when the argument list is the same

I have a lot of similar case classes which mean different things but have the same argument list.
object User {
case class Create(userName:String, firstName: String, lastName: String)
case class Created(userName:String, firstName: String, lastName: String)
}
object Group {
case class Create(groupName:String, members: Int)
case class Created(groupName:String, members: Int)
}
Given this kind of a setup, I was tired of writing methods that take an argument of type Create and return an argument of type Created. I have tons of test cases that do exactly this kind of thing.
I could write a function to convert one case class into the other. This function converts User.Create into User.Created
def userCreated(create: User.Create) = User.Create.unapply(create).map((User.Created.apply _).tupled).getOrElse(sys.error(s"User creation failed: $create"))
I had to write another such function for Group.
What I'd really like to have is a generic function that takes the two types of the case classes and an object of one case class and converts into the other. Something like,
def transform[A,B](a: A):B
Also, this function shouldn't defeat the purpose of reducing boilerplate. Please feel free to suggest a different signature for the function if that's easier to use.
Shapeless to the rescue!
You can use Shapeless's Generic to create generic representations of case classes, that can then be used to accomplish what you're trying to do. Using LabelledGeneric we can enforce both types and parameter names.
import shapeless._
case class Create(userName: String, firstName: String, lastName: String)
case class Created(userName: String, firstName: String, lastName: String)
case class SortOfCreated(screenName: String, firstName: String, lastName: String)
val c = Create("username", "firstname", "lastname")
val createGen = LabelledGeneric[Create]
val createdGen = LabelledGeneric[Created]
val sortOfCreatedGen = LabelledGeneric[SortOfCreated]
val created: Created = createdGen.from(createGen.to(c))
sortOfCreatedGen.from(createGen.to(c)) // fails to compile
For the record, here is the simplest typesafe syntax I've managed to implement:
implicit class Convert[A, RA](value: A)(implicit ga: Generic.Aux[A, RA]) {
def convertTo[B, RB](gb: Generic.Aux[B, RB])(implicit ev: RA =:= RB) =
gb.from(ga.to(value))
}
And it can be used like this:
case class Create(userName: String, firstName: String, lastName: String)
case class Created(userName: String, firstName: String, lastName: String)
val created = Create("foo", "bar", "baz").convertTo(Generic[Created])
Or the same thing with LabelledGeneric to achieve better type safety:
implicit class Convert[A, RA](value: A)(implicit ga: LabelledGeneric.Aux[A, RA]) {
def convertTo[B, RB](gb: LabelledGeneric.Aux[B, RB])(implicit ev: RA =:= RB) =
gb.from(ga.to(value))
}
val created = Create("foo", "bar", "baz").convertTo(LabelledGeneric[Created]))