I'm fairly new to Scala and I have a question about the best way to copy a case class while preserving data that comes from traits. For example, let's say I have the following:
trait Auditing {
var createTime: Timestamp = new Timestamp(System.currentTimeMillis)
}
case class User(val userName: String, val email: String) extends Auditing
val user = User("Joe", "joe#blah.com")
Then I want to make a new copy with one parameter changed:
val user2 = user.copy(email = "joe#newemail.com")
Now, in the example above, the property createTime does not get copied over because it is not defined in the constructor of the User case class. So my question is: assuming that moving createTime into the constructor is not an option, what is the best way for getting a copy of the User object that includes the value from the trait?
I'm using Scala 2.9.1
Thanks in advance!
Joe
You can override the copy method with that behavior.
case class User(val userName: String, val email: String) extends Auditing
{
def copy(userName = this.userName, email = this.email) {
val copiedUser = User(userName, email)
copiedUser.createTime = createTime
copiedUser
}
}
While I see no other solution than Reuben's, I don't understand the requirement to leave the constructor args untouched. This would be the most natural solution:
case class User(userName: String, email: String,
override val createTime:Timestamp = new Timestamp(System.currentTimeMillis))
extends Auditing
If you don't want the user to be able to overwrite createTime, you can still use:
case class User private (userName: String, email: String,
override val createTime:Timestamp) extends Auditing {
def this(userName: String, email: String) =
this(userName, email, new Timestamp(System.currentTimeMillis))
}
The only drawback is that you need to write new User("Joe", "joe#blah.com"), as the primary constructor is now private.
You might be better of not using a case class. You can easily implement the
functionality you need yourself. The below code implements the copy method you wanted, a constructor without new, hides the original constructor, and creates an extractor so that you can use User in case statements.
class User private(val userName: String,
val email: String,
val timeStamp: Timestamp =
new Timestamp(System.currentTimeMillis)) {
def copy(uName: String = userName,
eMail: String = email) =
new User(uName, eMail, timeStamp)
}
object User {
def apply(userName: String, email: String) =
new User(userName, email)
def unapply(u: User) = Some((u.userName, u.email, u.timeStamp))
}
Related
I reviewed some code from a colleague and I came across a case class which is by default immutable.
the below case class can be changed so my question is how is this possible since case classes are immutable but in this construct i can change the case class parameters?
case class RegisterCustomerRequest(`first-name`: String,
`last-name`: String,
`house-details`: String,
street: String,
zipcode: String,
city: String
extends WcRequestData {
def this(cardHolderData: CardHolderData,
registrationCode: RegistrationCode,
customerNumber: Long,
cardDesignImageId: String) =
this(`first-name` = cardHolderData.firstname,
`last-name` = cardHolderData.lastname,
street = cardHolderData.streetAndNumber,
zipcode = cardHolderData.zipCode,
city = cardHolderData.city,
# `house-details` =
s"${if (cardHolderData.employerName.contains("&"))
cardHolderData.employerName.replace("&" , " & ") else " /
"}${cardHolderData.employerName} ")#
}
why can I define a def this method which can change the values of parameters. What is this construct good for is this good coding style?
The case class RegisterCustomerRequest is still immutable however it has an auxiliary constructor def this which allows it to be constructed in a different way. For example, given
case class User(name: String)
case class Foo(name: String) {
def this(user: User) {
this(name = user.name)
}
}
we can construct Foo like so
Foo("picard")
or using the auxiliary constructor
new Foo(User("picard"))
In both cases the result is an immutable object. To confirm immutability try reassigning name after construction
(new Foo(User("picard"))).name = "worf" // Error: reassignment to val
As suggested by som-snytt, we can define apply method on companion object instead of auxiliary constructor like so
object Foo {
def apply(user: User): Foo = Foo(user.name)
}
which enables the following construction
Foo(User("picard"))
We're using case classes to represent the JSON objects transferred between the client and server. It's been working great except for one sticking point we've been living with for quite a while now and I wonder if anyone has a clever way around it.
Let's say I have a user object that has id, first name, last name and email address. Once a user has been saved to the database, he has an id (Int) assigned to him, so for all communication between the client and server dealing with existing users, the id is a required field. In fact, there is only one case when the id field is not required and that's when the user is first being saved. The way we currently deal with this is with a case class that looks like this:
case class User(id: Option[Int], firstName: String, lastName: String, email:String)
In all cases except the initial save, that id is Some and for the initial save id is always None so we find ourselves using id.getOrElse(0) quite often. (Sometimes we'll do a .get but it feels dirty.)
What I would love to have is an object with an id: Int field for existing users and an object with no id field at all for new users, but without declaring all the other fields twice in two separate case classes. However, I'm not seeing a way to do that conveniently. I'm also not fond of using a 'magic' number for the id field of new users.
Does anyone have a better solution to this issue?
case class User[+IdOpt <: Option[Int]](idOpt: IdOpt, firstName: String, lastName: String, email:String)
object User {
// Type aliases for convenience and code readability
type New = User[None.type]
type Saved = User[Some[Int]]
type Value = User[Option[Int]] // New or Saved
implicit class SavedOps(val user: Saved) extends AnyVal {
def id: Int = user.idOpt.get
}
}
Tests:
scala> val billNew = User(None, "Bill", "Gate", "bill#microsoft.com")
billNew: User[None.type] = User(None,Bill,Gate,bill#microsoft.com)
scala> billNew.id
<console>:17: error: value id is not a member of User[None.type]
billNew.id
^
scala> val billSaved = billNew.copy(idOpt = Some(1))
billSaved: User[Some[Int]] = User(Some(1),Bill,Gate,bill#microsoft.com)
scala> billSaved.id
res1: Int = 1
This is what we ended up going with for now.
trait Resource[T <: Option[Int]] {
def idOpt: T
}
object Resource {
type IsSome = Some[Int]
implicit class SomeOps[R <: Resource[IsSome]](val resource: R) {
def id: Int = resource.idOpt.get
}
}
This allows us to use it like this:
case class User[T <: Option[Int]](idOpt:T, firstName:String, lastName:String, email:String) extends Resource[T]
case class Company[T <: Option[Int]](idOpt:T, companyName: String) extends Resource[T]
val u1 = User(None, "Bubba", "Blue", "bubba#shrimp.com")
val u2 = User(Some(1), "Forrest", "Gump", "forrest#shrimp.com")
u1.id // <-- won't compile
u2.id // <-- compiles
Having a magic number is not a terrible idea if you hide it from the user. in fact it is a common pattern, Slick uses it for example. You can just ignore the id value for the objects to be inserted.
So you can start by making the the constructor package private
case class User private[db](id: Int, firstName: String, lastName: String, email:String)
And then provide a companion object for users to create it without id
object User{
def apply(firstName: String, lastName: String, email: String): User = User(-1, firstName, lastName, email)
}
And now you can construct it as if id wasn't required
val user = User("first","last","email")
there is Person model case class which is exposed to the client via REST endpoint.
scala> case class Person(firstname: String, lastname: String)
defined class Person
Now one of the internal service wants to "enrich" this Person case class and add social security number to it. BUT that enrichment is only to populate social security number from one service and pass it along to another one.
Immediate thought is to add ssn property to Person as follow - but that's not desired given Person is exposed to endpoint consumer and SSN MUST NOT be visible to any user calling REST endpoint which exposes Person data.
case class Person(firstname: String, lastname: String, ssn: String)
So another thought is to enrich Person case class as follow
scala> implicit class ExtendedPerson(person: Person){
| val SSN : String = ""
| }
defined class ExtendedPerson
then Service1.findPerson API will have implicit in the scope and will want to prepare Person as follow
val person = Person(firstName="Joe",lastname="Doe", ssn ="123-456-0000")
and then
Service2.moreWorkWithPerson(person,other arguments)
then Service2.moreWorkWithPerson will want to get ssn=123-456-0000 from person argument.
what's the right way to achieve this in Scala without modifying definition of Person case class? Can shapeless help? any pointers would be appreciated?
SSN as val in ExtendedPerson makes not much sense, instead it should be a function which queries your DB using the persons first and lastname. Given the fact that your DB is slow and you might call Person.SSN more than once I would suggest not to use this pattern as it will query your DB everytime you call .SSN. Instead I would write an implicit converter for Person to convert more explicitly.
case class ExtendedPerson(basicInfo: Person,ssn: String)
implicit class PersonHelper(person: Person) extends AnyVal{
def extended = {
val ssn = DB.querySSN(...)
ExtendedPerson(person,ssn)
}
}
val person: ExtendedPerson = Person("John","Doe").extended
//person = ExtendedPerson(Person("John","Doe"),"123-456-0000"")
Note: The code is written out of mind but should be logically correct
It seems that you are overthinking this. What's wrong with this?
case class Person(firstname: String, lastname: String, ssn: Option[String] = None);
Now, in REST handler you do return Person("John", "Doe"), and in the other service: return Person("John", "Doe", Some("111-11-1111"))
Maybe you should just expose a different class to the client.
case class Person(firstname: String, lastname: String, ssn: String) {
def toClient = ClientPerson(firstname, lastname)
}
case class ClientPerson(firstname: String, lastname: String)
object RESTService {
def getPerson(id: String): ClientPerson = {
val p: Person = DB.getPerson(id)
p.toClient
}
}
I have this concrete class:
class Person (var name: String, var surname: String)
and I want to create another class that extends Person:
class Son (name: String, surname: String) extends Person(name, surname)
OK.
But I do want the fields in the constructor of Son must be var and not val.
How do I fix this? The fields must remain constructor parameters.
UPDATE #2
My problem is as follows:
If I define a method in Son, this does not work if I change the value to the parameters of an instance of Son.
class Son (name: String, surname: String) extends Person(name, surname){
def printSon = {
if(this.name=="name")println("Error name Person")
if(this.surname=="surname")println("Error surname Person")
}
}
object main {
def main(args: Array[String]): Unit = {}
val Marco = new Son("Marco","Bianchi")
Marco.printSon // So far everything is ok
Marco.name="name"
Marco.printSon // Here in control is not done, because Marco.name="name" writes in Person
println("FINE")
}
name e surname in Son are of type val.
If I understand correctly you want the name and surname fields in Son to be immutable, as opposed to those in Person, which are vars.
That simply isn't possible. The only way to do it would be by overwriting:
class Person (var name: String, var surname: String)
//this won't compile
class Son(override val name: String, override val surname: String) extends Person(name, surname)
In Scala you cannot overwrite a var with a val - and it is pretty obvious why - subclasses can only override or add functionality to a superclass. If we wore to allow vals to override vars we would remove from the parent class the setter for the overridden field. This breaks the "is-a" relationship implied by inheritance.
What you could do is implement this like so:
trait Person {
def name: String
def surname: String
}
//this is fine now
class Son(val name: String, val surname: String) extends Person
//in case you want a type of person which can be changed.
class MutablePerson(var name: String, var surname: String) extends Person
Person now is a trait - it just provides a way of getting the name or surname. Son overrides Person by using vals - and thus you get the guarantee that nobody will change the fields in Son ever again.
From your example I assume that you still want to have a type of Person that can be modified. And you can do that using the MutablePerson class above, which does allow it's fields to be mutated.
Hope this helps !
You have to name the params different, because otherwise the methods in the body of son will try to access the params and not the fields in the Person class. Scala will automatically create fields for the params if you access them in the body. It will work, when you do something like this (which also is a commonly used pattern):
class Son (_name: String, _surname: String) extends Person(_name, _surname){
def printSon = {
if(name == "name")println("Error name Person")
if(surname == "surname")println("Error surname Person")
}
}
Is it possible to use named arguments in a Scala constructor, and later on override getters and setters without breaking the constructor interface or making the code extremely ugly?
Take the following bit of scala code
class Person( var FirstName: String, var LastName: String )
Nice and clean. This would create a simple class called person, which we could use in the following way
val john = new Person( FirstName="John", LastName="Doe" )
john.FirstName = "Joe"
println( john.FirstName )
Later, we decide we want to add some validation to the FirstName setter. As such, we create a new private local variable and override the getter and setter methods
class Person( var _FirstName: String, var _LastName: String ) {
def FirstName = _FirstName
def FirstName_= (value:String) = _FirstName = value
}
Still somewhat clean, however in order to do this, we've had to change the constructor argument names, thus breaking the external interface.
The first solution to this problem I came up with was
class Person {
var _FirstName:String = null
var LastName:String = null
def FirstName = _FirstName
def FirstName_= (value:String) = _FirstName = value
def this( FirstName: String, LastName: String ){
this()
this._FirstName = FirstName
this.LastName = LastName
}
}
Which is somewhat ugly and inelegant, and removes most of the nice reasons I was using scala in the first place.
Is there a better way of doing this?
tl;dr How to override getters/setters for members defined in the default constructor without making the code ugly or changing the public interface?
Did you consider using an companion object?
class Person private (f: String, l: String ) {
var FirstName = f
var LastName = l
}
object Person {
def apply(FirstName:String, LastName:String) =
new Person(FirstName, LastName)
}
If you're not already using implicit conversions to create the arguments, you can do something like this:
def validateName(s: String) = {
if (s.length>0 && s(0).isUpper) s
else throw new IllegalArgumentException(s+" is not a name!")
}
object Example {
private[Example] class ValidatedName(val s: String) { }
class Person(var firstName: ValidatedName, var lastName: String) { }
implicit def string2valid(s: String) = new ValidatedName(validateName(s))
implicit def valid2string(v: ValidatedName) = v.s
}
scala> new Example.Person("Joe","Schmoe")
res17: Example.Person = Example$Person#51887dd5
scala> new Example.Person("ee","cummings")
java.lang.IllegalArgumentException: ee is not a name!
It's not binary compatible, but it is source compatible (again, if the names weren't already relying upon implicit conversions).
Another slightly longer possibility is to create a stealth ancestor:
class CheckedPerson(private var first: String, var lastName: String) {
def firstName = first
def firstName_=(s: String) { first = validateName(s) }
}
class Person(firstName: String, lastName: String) extends
CheckedPerson(validateName(firstName),lastName) { }
for which I'm not sure about binary compatibility, but will definitely give source compatibility.
No. There is currently no way to do that, it's currently not the focus of research.
It is one of my major pet peeves I have with the language: There is no sensible way to combine constructor arguments and self-defined getter/setter methods.
If you're not happy with the functionality class Person( var FirstName: String, var LastName: String ) provides, it basically means "back to Java's verboseness".