Scala. Case class copy from trait reference - scala

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?

Related

Scala trait implementation

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.

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

Implement abstract method with return type of self

I am working on a web app where I have a bunch of models.
Every model extends and implements the following abstract class:
abstract class BaseModel {
val dateCreated: String
val dateUpdated: String
}
trait BaseModelCompanion[A <: BaseModel] {
implicit val reads: Reads[A]
implicit val writes: Writes[A]
}
Example:
case class User(id: String, name: String, dateCreated: String, dateUpdated: String) extends BaseModel {
...
}
object User extends BaseModelCompanion[User] {
implicit val reads = Reads[User] = (...)
implicit val writes = new Writes[User] { ... }
}
Now I want to add an abstract method to my BaseModel called update, where each model will take in some Json and return a clone of itself. This is what an implementation would look like:
case class User(id: String, dateCreated: String, dateUpdated: String) extends BaseModel {
// Every model needs one of these
def update(jsValue: JsValue): User = {
copy(
name = (jsValue \ "name").as[String].get,
...
)
}
}
My problem is that I am struggling to define the abstract method signature:
abstract class BaseModel {
val dateCreated: String
val dateUpdated: String
def update(jsValue: JsValue): ______ // How do I say return an object of type "self"
}
You can use F-bounded polymorphism
abstract class BaseModel[A <: BaseModel[A]] {
val dateCreated: String
val dateUpdated: String
def update(jsValue: String): A
}
case class User(id: String, dateCreated: String, dateUpdated: String) extends BaseModel[User] {
def update(jsValue: String): User = {
copy(id = jsValue)
}
}
EDIT by oxbow_lakes
The feature you are hoping for has been called mytype and has been suggested in the past as trivially implementable by scalac as an additional language feature (by #extempore). IIRC it was rejected at the time because of some obscure corner cases. (See the distant history of the scala mailing lists)

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