Case class immutable still able to change the parameter values - scala

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

Related

Factory with companion object where each type of object takes a common parameter

I have a class like this -
class Cache (
tableName: String,
TTL: Int) {
// Creates a cache
}
I have a companion object that returns different types of caches. It has functions that require a base table name and can construct the cache.
object Cache {
def getOpsCache(baseTableName: String): Cache = {
new Cache(s"baseTableName_ops", OpsTTL);
}
def getSnapshotCache(baseTableName: String): Cache = {
new Cache(s"baseTableName_snaps", SnapshotTTL);
}
def getMetadataCache(baseTableName: String): Cache = {
new Cache(s"baseTableName_metadata", MetadataTTL);
}
}
The object does a few more things and the Cache class has more parameters, which makes it useful to have a companion object to create different types of Caches. The baseTableName parameter is same for all of the caches. Is there a way in which I can pass this parameter only once and then just call the functions to get different types of caches ?
Alternative to this is to create a factory class and pass the baseTableName parameter to constructor and then call the functions. But I am wondering if it could be done in any way with the Companion object.
The simplest way is to put your factory in a case class:
case class CacheFactory(baseTableName: String) {
lazy val getOpsCache: Cache =
Cache(s"baseTableName_ops", OpsTTL)
lazy val getSnapshotCache =
Cache(s"baseTableName_snaps", SnapshotTTL)
lazy val getMetadataCache =
Cache(s"baseTableName_metadata", MetadataTTL)
}
As I like case classes I changed your Cache also to a case class:
case class Cache(tableName: String, TTL: Int)
As you can see I adjusted your Java code to correct Scala code.
If you want to put it in the companion object, you could use implicits, like:
object Cache {
def getOpsCache(implicit baseTableName: String): Cache =
Cache(s"baseTableName_ops", OpsTTL)
def getSnapshotCache(implicit baseTableName: String) =
Cache(s"baseTableName_snaps", SnapshotTTL)
def getMetadataCache(implicit baseTableName: String) =
Cache(s"baseTableName_metadata", MetadataTTL)
}
Then your client looks like:
implicit val baseTableName: String = "baseName"
cache.getSnapshotCache
cache.getMetadataCache
Consider creating algebraic data type like so
sealed abstract class Cache(tablePostfix: String, ttl: Int) {
val tableName = s"baseTableName_$tablePostfix"
}
case object OpsCache extends Cache("ops", 60)
case object SnapshotCache extends Cache("snaps", 120)
case object MetadataCache extends Cache("metadata", 180)
OpsCache.tableName // res0: String = baseTableName_ops

Passing different object models as a parameter to a method in scala

I've really struggled with type relationships in scala and how to use them effectively. I am currently trying to understand how I would use them to only edit certain fields in a Mongo Collection. This means passing a particular object containing only those fields to a method which (after reading about variances) I thought that I could do like this:
abstract class DocClass
case class DocPart1(oId: Option[BSONObjectID], name: String, other: String) extends DocClass
case class DocPart2(city: String, country: String) extends DocClass
With the method that calls a more generic method as:
def updateMultipleFields(oId: Option[BSONObjectID], dataModel: DocClass): Future[Result] = serviceClientDb.updateFields[T](collectionName, dataModel, oId)
// updateFields updates the collection by passing *dataModel* into the collection, i.e. Json.obj("$set" -> dataModel)
So dataModel can be a DocPart1 or DocPart2 object. I'm eager not to use a
type parameter on updateMultipleFields (as this interesting article may suggest) as this leads me to further issues in passing these to this method in other files in the project. I'm doing this to abide with DRY and in order to maintain efficient database operations.
I've gone round in circles with this one - can anyone shed any light on this?
Edited after #SerGr's comments
So to be completely clear; I'm using Play/Scala/ReactiveMongo Play JSON (as documented here) and I have a MongoDB collection with lots of fields.
case class Doc(oId: Option[BSONObjectID], name: String, city: String, country: String, city: String, continent: String, region: String, region: String, latitude: Long, longitude: Long)
To create a new document I have auto-mapped Doc (above) to the collection structure (in Play - like this) and created a form (to insert/update the collection) - all working well!
But when editing a document; I would like to update only some fields (so that all of the fields are not updated). I have therefore created multiple case classes to divide these fields into smaller models (like the examples of DocPart1 & DocPart2) and mapped the form data to just one. This has led me to pass these as a parameter to the updateMultipleFields method as shown above. I hope that this makes more sense.
I'm not sure if I understand correctly what you need. Still here is some code that might be it. Assume we have our FullDoc class defined as:
case class FullDoc(_id: Option[BSONObjectID], name: String, other: String)
and we have 2 partial updates defined as:
sealed trait BaseDocPart
case class DocPart1(name: String) extends BaseDocPart
case class DocPart2(other: String) extends BaseDocPart
Also assume we have an accessor to our Mongo collection:
def docCollection: Future[JSONCollection] = ...
So if I understand your requirements, what you need is something like this:
def update[T <: BaseDocPart](oId: BSONObjectID, docPart: T)(implicit format: OFormat[T]) = {
docCollection.flatMap(_.update(BSONDocument("_id" -> oId),
JsObject(Seq("$set" -> Json.toJson(docPart)))))
}
Essentially the main trick is to use generic T <: BaseDocPart and pass implicit format: OFormat[T] so that we can convert our specific child of BaseDocPart to JSON even after type erasure.
And here is some additional test code (that I used in my console application)
implicit val fullFormat = Json.format[FullDoc]
implicit val part1Format = Json.format[DocPart1]
implicit val part2Format = Json.format[DocPart2]
def insert(id: Int) = {
val fullDoc = FullDoc(None, s"fullDoc_$id", s"other_$id")
val insF: Future[WriteResult] = docCollection.flatMap(_.insert(fullDoc))
val insRes = Await.result(insF, 2 seconds)
println(s"insRes = $insRes")
}
def loadAndPrintAll() = {
val readF = docCollection.flatMap(_.find(Json.obj()).cursor[FullDoc](ReadPreference.primaryPreferred).collect(100, Cursor.FailOnError[Vector[FullDoc]]()))
val readRes = Await.result(readF, 2 seconds)
println(s"readRes =\n${readRes.mkString("\n")}")
}
def loadRandomDocument(): FullDoc = {
val readF = docCollection.flatMap(_.find(Json.obj()).cursor[FullDoc](ReadPreference.primaryPreferred).collect(100, Cursor.FailOnError[Vector[FullDoc]]()))
val readRes = Await.result(readF, 2 seconds)
readRes(Random.nextInt(readRes.length))
}
def updateWrapper[T <: BaseDocPart](oId: BSONObjectID, docPart: T)(implicit writer: OFormat[T]) = {
val updateRes = Await.result(update(oId, docPart), 2 seconds)
println(s"updateRes = $updateRes")
}
// pre-fill with some data
insert(1)
insert(2)
insert(3)
insert(4)
val newId: Int = ((System.currentTimeMillis() - 1511464148000L) / 100).toInt
println(s"newId = $newId")
val doc21: FullDoc = loadRandomDocument()
println(s"doc21 = $doc21")
updateWrapper(doc21._id.get, DocPart1(s"p1_modified_$newId"))
val doc22: FullDoc = loadRandomDocument()
println(s"doc22 = $doc22")
updateWrapper(doc22._id.get, DocPart2(s"p2_modified_$newId"))
loadAndPrintAll()

Modify one value in a Scala class constructor in a concise way

If I want to modify one single parameter in a constructor.
In the Scala case class, the apply method will be overridden twice. Unless apply applies ( no pun ) to auxiliary constructor.
Related to
Modifying case class constructor parameter before setting value
How to override apply in a case class companion
How one can modify one single input from a constructor ?
Criteria :
The class must hold immutable data. All data must be accessible.
Note it doesn't have to be case class.
Additionally , no need to use the apply method.
no extra unused parameters. In the example below, _fistName is still accessible but unused.
case class Person(
lastName: String,
_fistName: String, ... )
{ lazy val fistName = _fistName.toLowerCase }
Here are two simple approaches.
Using class:
class Person(first: String, last: String) {
val firstName = first.toLowerCase
val lastName = last.toLowerCase()
}
val person = new Person("Adam", "Smith")
println(person.firstName + " " + person.lastName) // adam smith
Using trait + object's apply():
trait Person {
val firstName: String
val lastName: String
}
object Person {
def apply(first: String, last: String) = new Person {
override val firstName: String = first.toLowerCase
override val lastName: String = last.toLowerCase
}
}
val person = Person("Adam", "Smith")
println(person.firstName + " " + person.lastName) // adam smith
Note that classes must be instantiated with new, while traits (created with apply()) don't.
Why no case classes? Well, they are designed to serve as ADTs (abstract data types). Basically, they are thought of as containers for some data without any logic. That's why they get apply() out-of-the-box. If you want to override it, then means your class doesn't have the semantics of a case class.
I can see that #prayag took the effort of trying to help you force it into a case class, but seriously, if it doesn't have to be one (and you said so in the question), then don't make it one.
The reference you had posted seems to have lot of answers as well.
Two simple ways I could think of
make it abstract case class and define companion object which would mutate the value you want
define the member of case class as var and mutate it.
eg. (using scalatest)
class CaseClassSpecs extends FunSpec {
describe("case class modify") {
it("APPROACH 1 : modifies abstract case class member") {
object Item {
def apply(itemId: String, itemName: String) :Item = new Item(itemId.toLowerCase, itemName) {}
}
abstract case class Item private (val itemId: String, itemName: String)
val item1 = Item("SKU-ONE", "Shirts")
assert(item1.itemId == "sku-one")
assert(item1.itemName == "Shirts")
}
it("APPROACH 2 : modifies case class member which is var") {
case class Item (var itemId: String, itemName: String) {
itemId = itemId.toLowerCase()
}
val item1 = Item("SKU-ONE", "Shirts")
assert(item1.itemId == "sku-one")
assert(item1.itemName == "Shirts")
}
}
}
Class parameters are not necessarily class members. You can have class parameters that do not become class members.
Method 1
class Foo(bar0: String) {
val bar = bar0.toLowerCase()
}
#main
def main(): Unit = {
println(Foo("AsDfGh").bar)
}
prints:
asdfgh
and the decompiled code is:
public class Foo {
private final String bar;
public Foo(final String bar0) {
this.bar = bar0.toLowerCase();
}
public String bar() {
return this.bar;
}
}
You see, bar0 is a "temporary" value, it does not become a field because it is not referenced.
So if you want to change a value, just do not use the original value in the methods.
Method 2
For case classes, it does not seem to work in 2022, but here is another trick:
case class Point (x: Double, y: Double)
class PolarPoint(r: Double, alpha: Double) extends Point(r*Math.cos(alpha), r*Math.sin(alpha))
Here r and alpha do not become members of PolarPoint.
If you don't need two types, you can make the 1st constructor protected:
case class Foo protected(x:Int)
class FooImpl(x0:Int) extends Foo(myFunc(x0))
You will reference objects as Foos but create FooImpls.
Method 3
Your class can have multiple parameter lists and implicits:
class Qux(x:String)(implicit val y:String = x.toLowerCase())
is converted to:
public class Qux {
private final String y;
public static String $lessinit$greater$default$2(String var0) {
return Qux$.MODULE$.$lessinit$greater$default$2(var0);
}
public Qux(final String x, final String y) {
this.y = y;
}
public String y() {
return this.y;
}
}
You see that here only y becomes a field.

How to initialize one field using another in case class?

Suppose I have a case class like
case class Person(fname:String, lname:String, nickName:Option[String] = None)
On creating an instance like Person("John", "Doe"), I want nickName to be automatically assigned to fname, if one is not given. Eg:
val p1 = Person("John", "Doe")
p1.nickName.get == "John"
val p2 = Person("Jane", "Doe", "Joe")
p2.nickName.get == "Joe"
How can auto assignment of one field from another field be achieved?
Trying the solutions below in repl. I think this is something to do with repl
scala> case class Person(fname: String, lname:String, nickName:Option[String])
defined class Person
scala> object Person { def apply(fname:String, lname:String):Person = {Person(fname, lname, Some(fname))}}
console:9: error: too many arguments for method apply: (fname: String, lname: String)Person in object Person
object Person { def apply(fname:String, lname:String):Person = {Person(fname, lname, Some(fname))}}
You can overload the constructor of the case class
case class Foo(bar: Int, baz: Int) {
def this(bar: Int) = this(bar, 0)
}
new Foo(1, 2)
new Foo(1)
So you can check the case if nickName is none.
You can also overload it's apply method in the same way. In that way, then you can use
Foo(1,2)
Foo(1)
Technical Solution (don't)
On the current definition of case classes, you can override the constructor of the case class and the apply method of its companion object, as described in the answer of Facundo Fabre.
You will get something like this:
object Person {
def apply(fname:String, lname:String): Person = Person(fname, lname, fname)
}
case class Person(fname:String, lname:String, nickName: String) {
def this(fname:String, lname:String) = this(fname, lname, fname)
}
This is technical correct and quite clever coding. But for my taste its a little bit too clever, because it breaks an important property:
CaseClass.unapply(CaseClass.apply(x1,x2,x3)) == (x1,x2,x3)
In other words: When I construct a case class using apply with some tuple of parameter and then deconstruct it using unapply I expect to get the original tuple I put into apply (ignoring currying and option type).
But in this case, this property is not true any more:
Person.unapply(Person("John", "Smith"))
// result: Option[(String, String, String)] = Some((John,Smith,John))
Deconstruction using unapply is used for pattern matching (match{ case ... => ...). And this is a common use case for case classes.
So while the code is quite clever, it might confuse other people and break properties their code relies on.
Rephrase the problem (my suggestion)
When overly clever code is needed, it is often a good idea to rethink, what problem one tries to solve. In this case, I would suggest to distinguish between the nick name the user has chosen and a nick the system assigns to the user. In this case I would then just build a case class like this:
case class NickedPerson(fname : String, lname : String, chosenNick : Option[String] = None) {
val nick = chosenNick.getOrElse(fname)
}
You can then just use the field nick to access the computed nick name, or use chosenNick if you want to know if the user has provided that nick name:
NickedPerson("John", "Smith").nick
// result: String = "John"
NickedPerson("John", "Smith", Some("Agent")).nick
// result: String = "Agent"
The basic properties about case classes are not changed by this code.
Just explaining how to overload apply from companion object (in addition to #Facundo Fabre answer):
scala> :paste
// Entering paste mode (ctrl-D to finish)
object Person {
def apply(fname:String, lname:String): Person = Person(fname, lname, fname)
}
case class Person(fname:String, lname:String, nickName: String)
// Exiting paste mode, now interpreting.
defined object Person
defined class Person
scala> Person("aaa", "bbb")
res24: Person = Person(aaa,bbb,aaa)
scala> Person("aaa", "bbb", "ccc")
res25: Person = Person(aaa,bbb,ccc)
You could also define default value using muti-parameter list constructor, but it's hard to use such case class (no toString and pattern matching for last parameter), so won't recommend (but it's good if you want simmilar thing for methods):
scala> case class Person(fname:String, lname:String)(val nickName: String = fname)
defined class Person
Another funny solution (just to play), which I wouldn't recommend to use in real code:
scala> case class Person(fname:String, lname:String, var nickName: String = null){nickName = Option(nickName).getOrElse(fname)}
defined class Person
scala> Person("aaa", "bbb")
res32: Person = Person(aaa,bbb,aaa)

Case classes with inheritance and default parameters

Is there an elegant way in Scala to use case classes (or case classish syntax) with inheritance and default constructor parameters? I kind of want to do this (without repeating the default parameters of the superclass):
abstract class SuperClazz(id: String = "")
case class SubClazz(name: String = "", size: Int = 0) extends SuperClazz
val sub = SubClazz(id = "123", size = 2)
I would say it's not possible to do without repeating parameters from super class. It is due to the fact that case classes are special type of scala classes. It is beacuse compiler implicitly generates companion extractor object with apply and unapply methods and in those methods it will be no parameter that is not specified in class parameters.
Consider this code snippet
abstract class SuperClazz(id: String = "")
class SubClazz(name: String,id: String) extends SuperClazz {
override def toString: String = "name: " + name + ",id: " + id
}
object SubClazz {
def apply(name: String = "", id: String = "") = new SubClazz(name, id)
}
It's shorter and simpler ( and little bit different regarding toString method ) version of what is being created when
case class SubClazz(name: String, id: String) extends SubClazz
is called.