Is there exist in Scala some syntaxic sugar to "immutable class setters"?
Here is an example:
class Bob (val x:Int, val y:String)
In order to change x or y, I can implement it this way :
object Bob {
def updX (b:Bob) (x:Int) = new Bob (x, b.y)
def updY (b:Bob) (y:String) = new Bob (b.x, y)
}
class Bob (val x:Int, val y:String) {
def updX (x:Int) = Bob.updX(this)(x)
def updY (y:String) = Bob.updY(this)(y)
}
This solution is terrible because some of my classes have 8 parameters.
Is there a better way to achieve this ?
If you make your class a case class, you will get a copy method for free. Case class parameters are automatically immutable, so no need to put val in front of them.
case class Bob(x: Int, y: String)
val bob1 = Bob(1, "Bob1")
val bob2 = bob1.copy(y = "Bob2")
As #insan-e mentions in his answer, the pattern that is used by case classes, is a copy method with parameters matching the constructor and default values matching the vals. You can easily implement that yourself:
class Bob(val x: Int, val y: String) {
def copy(x: Int = x, y: String = y) = new Bob(x, y)
}
Related
Say I have the following trait:
trait T {
val x: Int
}
def foo(i: Int): T
I would like to bet able to write and read this trait using upickle without knowing what the specific class is. e.g.:
upickle.default.write(foo(3))
Such that I could elsewhere define foo to be something like:
case class A(x: Int)
def foo(i: Int): T = A(i)
I am thinking I need to define an implicit Writer as a member of T but I don't know what the appropriate syntax would be?
trait T {
val x: Int
}
object T {
implicit val rw: ReaderWriter[T] = ...
}
The problem is what to put into the ... part: if you have a T value, you can just store its x:
... = readwriter[Int].bimap[T](
t => t.x,
i => new T { val x = i }
)
The problem with this solution is that reading a written A(3) won't return an A. And this isn't really solvable without making T sealed or otherwise handling a specific set of subclasses only.
You could include a class name as well when writing, but that won't help if the class has any fields other than x to store:
class B(override val x: Int, y: String) extends T
If T is sealed, you just write
... = macroRW
Let's say I have the following hierarchy:
abstract class A(val x: Int, val y: String)
class B(override val x: Int, override val y: String, val z: Int) extends A(x,y)
Now I want to initialize the values from a configuration object but I want the actual values to be the original ones.
If I would do the configuration in B only then I would do something like:
class B(override val x: Int, override val y: String, val z: Int) extends A(x,y)
def this(conf: Conf) {
this(conf.get("x"), conf.get("y"), conf.get("z"))
}
but I want to be able to do the same in A.
If I add:
abstract class A(val x: Int, val y: String)
this(conf: Conf) {
this(conf.get("x"), conf.get("y))
}
I wouldn't be able to define B (I don't have the conf in the B default constructor).
EDIT:
To make this clearer:
The use case I have is a factory which generates the proper B (there are a large number of child classes). It does so by doing something like:
def getElement(elemType: String, conf: Conf): A = {
elemType match {
case "B" => new B(conf)
}
}
Currently, I have a companion object:
object B {
def apply(conf: conf) = B(conf.getx(), conf.gety(), ...)
}
The problem is that when I need to add a new element to the parent A, I need to go and change every one of the children and I have the same code conf.getx(), conf.gety() etc.
Ideally I would like B constructor to be able to do something like:
class B(conf: Conf) extends A(conf)
but I can't do this as this would make conf into a member of B.
You can also use companion objects to define alternative constructors:
case object A {
def apply(conf: Conf): A = new A(conf.get("x"), conf.get("y"))
}
case object B {
def apply(conf: Conf): B = new B(conf.get("x"), conf.get("y"), conf.get("z"))
}
After looking around some more I found this (also points to this) and this. All three basically say the following:
If we use an argument without val or var and it is only referenced in the constructor then it does NOT become a member.
This means the following solution would work:
abstract class A(conf: Conf) {
val x = conf.getX()
val y = conf.getY()
}
class B(conf: Conf) extends A(conf) {
val z = conf.getZ()
}
would provide the required behavior cleanly and simply.
I was thinking about something, I wrote this trait with two classes extending it and another class that may contain them:
sealed trait MainObj
case object subObj1 extends mainObj
case Object subObj2 extends mainObj
case class AnotherClass(val mo: Option[MainObj], val amount: Int)
Now let's say I wanted to write a function that added the amounts in twoAnotherClass objects but only if the optional MainObj inside each of them were the same type. Like only add if both are subObj1.
So I could do something like this:
def add(one: AnotherClass, two: AnotherClass): AnotherClass = one. mo, two.mo match {
case (Some(x), Some(y)) if i and x are the same class => return a `AnotherClass` with the two integers added from `one` and `two`
etc..
I wondered whether there was another way of doing this? I don't know whether this is a "functional" way of doing it or whether there is a less verbose way?
I ask because essentially I could write some more methods like subtract, multiply, etc ... and I have some very common code.
I could extract that common code and write a function that simply takes two of the subObjs and an operation and then apply that operation on the two objects, but that only abstracts out the common operation, could the main pattern matching part be written differently that may be considered more concise(for lack of a better word)?
Just add the 'add' method to the case class :
sealed trait MainObj
case object SubObj1 extends MainObj
case object SubObj2 extends MainObj
case class AnotherClass(val mo: Option[MainObj], val amount: Int){
def add(that:AnotherClass):AnotherClass = {
if (that.mo == this.mo)
this.copy(amount = this.amount + 1)
else
this
}
}
val x = AnotherClass(Some(SubObj1), 1)
val y = AnotherClass(Some(SubObj1), 1)
val z = AnotherClass(Some(SubObj2), 1)
scala> x add y
res5: AnotherClass = AnotherClass(Some(SubObj1),2)
scala> x add z
res6: AnotherClass = AnotherClass(Some(SubObj1),1)
scala> val l = List(x,y,z)
l.reduce ( _ add _)
res7: AnotherClass = AnotherClass(Some(SubObj1),2)
val mo = two.mo
one match {
case AnoterClass(`mo`, am) =>
one.copy(amount = am + two.amount)
case _ => ???
}
I have several case classes with a count field. It's 1 by default, and I have a reduce in my code that groups duplicates and sums that value to find the number of each object. E.g.:
case class Person(name: String, count = 1)
personList.groupBy(_.name).reduce((x,y) => x.copy(count = x.count + 1))
I have this logic in several case classes, and since my logic is a bit more complicated than the example above I want to create a generic merging function.
So I've created a sealed trait with a count field. I've then changed my case classes to extend from this, e.g.:
case class Person(name: String, override val count) extends Countable
So far, so good.
However, I can't work out how to declare my merge function so that it only accepts case classes that extend Countable. Because of that, it can't find the copy method.
Here's what I have:
def merge[T <: Countable](f: T => Seq[String])(ms: Seq[T]): Vector[T] =
ms.groupBy(x => f(x).mkString("_")).mapValues(_.reduce { (x,y) =>
x.copy(count = x.count + 1) // can't find `copy`
}).values.toVector
Is there a typeclass that I can also include that means a type has a copy method (or is a case class) using Scala 2.11.7?
Update:
Countable trait is:
sealed trait Countable {
def timesSeen: Long = 1
}
How did you defined you Countable trait.
Following snippet works fine for me:
trait Countable[Z] {
def count: Int
def copy: Z
}
case class Person(name: String, override val count: Int) extends Countable[Person] {
override def copy: Person = this
}
def merge[T <: Countable[T]](f: T => Seq[String])(ms: Seq[T]): Vector[T] = {
val r = ms.groupBy(x => f(x).mkString("_")).mapValues(_.reduce { (x, y) =>
x.copy
}).values.toVector
r
}
Is there a way to rely on methods defined in case class in a trait? E.g., copy: the following doesn't work. I'm not sure why, though.
trait K[T <: K[T]] {
val x: String
val y: String
def m: T = copy(x = "hello")
def copy(x: String = this.x, y: String = this.y): T
}
case class L(val x: String, val y: String) extends K[L]
Gives:
error: class L needs to be abstract, since method copy in trait K of type
(x: String,y: String)L is not defined
case class L(val x: String, val y: String) extends K[L]
^
A solution is to declare that your trait must be applied to a class with a copy method:
trait K[T <: K[T]] {this: {def copy(x: String, y: String): T} =>
val x: String
val y: String
def m: T = copy(x = "hello", y)
}
(unfortunately you can not use implicit parameter in the copy method, as implicit declaration is not allowed in the type declaration)
Then your declaration is ok:
case class L(val x: String, val y: String) extends K[L]
(tested in REPL scala 2.8.1)
The reason why your attempt does not work is explained in the solution proposed by other users: your copy declaration block the generation of the "case copy" method.
I suppose that having method with name copy in trait instructs compiler to not generate method copy in case class - so in your example method copy is not implemented in your case class. Below short experiment with method copy implemented in trait:
scala> trait K[T <: K[T]] {
| val x: String
| val y: String
| def m: T = copy(x = "hello")
| def copy(x: String = this.x, y: String = this.y): T = {println("I'm from trait"); null.asInstanceOf[T]}
| }
defined trait K
scala> case class L(val x: String, val y: String) extends K[L]
defined class L
scala> val c = L("x","y")
c: L = L(x,y)
scala> val d = c.copy()
I'm from trait
d: L = null
You can run repl with $scala -Xprint:typer. With parameter -Xprint:typer you can see what exactly happening when you create trait or class. And you will see from output that method "copy" not created, so compiler requests to define it by yourself.