Say I have some case class in a library:
case class MyClass(a: Int, b: Int)
Later it turns out that there's a bug in my library and I need to apply some extra logic to one of these parameters to keep things working, so that from the user's perspective instances this can happen:
val x = MyClass(1, 2)
println(x.a) // prints '3' or whatever I happen to compute for 'a'
In other words, the final value for x.a is not necessarily what was passed in to the constructor. I know this looks crazy, but trust me, I need it. x.a will still return whatever was passed to the constructor in most cases, but there is one value for the constructor parameter that will lead to bugs and I need to transform it.
I see two ways to achieve this. I can make a a var:
case class MyClass(var a: Int, b: Int) {
a = someComputation()
}
but then the class becomes mutable because a can be set from the outside. My problem would be solved if I could remove or 'hide' the generated setter but it doesn't seem to be possible. If I add
private def a_=(newA: Int) {}
it doesn't override the setter generated by the var so it sees two method definitions and doesn't compile.
The second option is to create a field/method separate from the constructor parameter:
case class MyClass(private val _a: Int, b: Int) {
val a = someComputation(a)
}
but _a is used in all the special generated methods such as equals, toString, etc, whereas the custom field a doesn't feature.
Is there any way to transform the constructor parameters without affecting the rest of the API?
What I'd do, is override the apply method on the companion object to create an instance of MyClass with the right computation.
object MyClass {
def apply(a: Int, b: Int) = new MyClass(someComputation(a),b))
}
Then you can call it like val x = MyClass(1, 2), but you won't be able to call it like val x = new MyClass(1, 2) if you still want the computation to occur.
Apparently all of the above do not work outside the REPL.
Instead I'd settle on another method on the companion object, it's not as nice a solution, but it should work:
object MyClass {
def create(a: Int, b: Int) = new MyClass(someComputation(a),b))
}
So, you want something like MyClass(a=1).a == 2 to return true?
Do you really need an explanation why it is a bad idea? ;)
Thank god, it is not possible!
Related
In scala, it is legal to write case class Foo(private val bar: Any, private val baz: Any).
This works like one might hope, Foo(1, 2) == Foo(1, 2) and a copy method is generated as well. In my testing, marking the entire constructor as private marks the copy method as private, which is great.
However, either way, you can still do this:
Foo(1, 2) match { case Foo(bar, baz) => bar } // 1
So by pattern matching you can extract the value. That seems to render the private in private val bar: Any more of a suggestion. I saw someone say that "If you want encapsulation, a case class is not the right abstraction", which is a valid point I reckon I agree with. But that raises the question, why is this syntax even allowed if it is arguably misleading?
It's because case classes were not primary intended to be used with private constructors or fields in the first place. Their main purpose is to model immutable data. As you saw there are workarounds to get the fields so using a private constructor or private fields on a case class is usually a sign of a code smell.
Still, the syntax is allowed, because the code is syntactically and semantically correct - as far as the compiler is concerned. But from the programmer's point of view is at the limit of "does it makes sense to be used it like that?" Probably not.
Marking the constructor of a case class as private does not have the effect you want, and it does not make the copy method private, at least not in Scala 2.13.
LATER EDIT: In Scala 3, marking the constructor as private, does make the apply and copy methods private. This change was also developed for Scala 2 and can be found here - but it was delayed for the 2.14 future release. The reason it can't go into Scala 2.13 is because it's a breaking change.
case class Foo private (bar: Int, baz: Int)
The only thing I can't do is this:
val foo1 = new Foo(1, 2) // no constructor accessible from here
But I can do this:
val foo2 = Foo(1, 2) // ok
val foo3 = Foo.apply(1, 2) // ok
val foo4 = foo2.copy(4) // ok - Foo(4,2)
A case class as it's name implies means the object is precisely intended to be pattern matched or "caseable" - that means you can use it like this:
case Foo(x, y) => // do something with x and y
Or this:
val foo2 = Foo(1, 2)
val Foo(x, y) = foo2 // equivalent to the previous
Otherwise why would you mark it as a case class instead of a class ?
Sure, one could argue a case class is more convenient because it comes with a lot of methods (rest assured, all that comes with an overhead at runtime as a trade-off for all those created methods) but if privacy is what you are after, they don't bring anything to the table on that.
A case class creates a companion object with an unapply method inside which when given an instance of your case class, it will deconstruct/destructure it into its initialized fields. This is also called an extractor method.
The companion object and it's class can access each other's members - that includes private constructor and fields. That is how it was designed. Now apply and unapply are public methods defined on the companion object which means - you can still create new objects using apply, and if your fields are private - you can still access them from unapply.
Still, you can overwrite them both in the companion object if you really want your case class to be private. Most of the times though, it won't make sense to do so, unless you have some really specific requirements:
case class Foo2 private (private val bar: Int, private val baz: Int)
object Foo2 {
private def apply(bar: Int, baz: Int) = new Foo2(bar, baz)
private def unapply(f: Foo2): Option[(Int, Int)] = Some(f.bar, f.baz)
}
val foo11 = new Foo2(1, 2) // won't compile
val foo22 = Foo2(1, 2) // won't compile
val foo33 = Foo2.apply(1, 2) // won't compile
val Foo2(bar, baz) = foo22 // won't compile
println(Foo2(1, 2) == Foo2(1, 2)) // won't compile
val sum = foo22 match {
case Foo2(x, y) => x + y // won't compile
}
Still, you can see the contents of Foo2 by printing it, because case classes also overwrite toString and you can't make that private, so you'll have to overwrite it to print something else. I will leave that to you to try out.
print(foo11) // Foo2(1,2)
As you see, a case class brings multiple access points to it's constructor and fields. This example was just for understanding the concept. It is not an example of a good design. Usually in OOP, you need an instance of some class, to perform operations on it. So a class that you cannot instantiate at all is no more useful than a Scala object. If you find yourself blocking all ways to create an instance of some class or case class, that's a sign you probably need an object instead since object is already a singleton in Scala.
Adding to the previous answer: you can make the copy method inaccessible by adding a private method named copy:
case class Foo3(private val x: Int) {
private def copy: Foo3 = this
}
Foo3(1).copy(x = 2) // won't compile ("method ... cannot be accessed")
lets say that for an example I have a data class that's like this
data class AB(a: String, b: String)
how do I make a constructor (or if there is any other way) handle a different type, for example if get an "a" variable as an Int and I want to handle it and covert it to a string?
Thanks!
The obvious answer is using a secondary constructor, but this might be too restrictive in what it does. In this case, probably only really a good idea if you want a simple .toString() call. (see a)
You could also put the conversion logic in some function defined outside your class or in the companion object (see b). But I feel like you'd usually end up with more readable code by just using a factory method on the companion object at that point (see c).
data class AB(val a: String){
constructor(a: Double): this(a.toString())
constructor(b: Int): this(fromInt(b))
companion object {
fun fromInt(b: Int) = "this is my string: $b"
fun create(c: Int) = AB("this is my string: $c")
}
}
val ab: AB = AB.create(2)
I am trying to create a method that will take a list of Objects and a list of Types and then check whether a particular object matches a particular type.
I tried to create a dummy example. In the following code validate method takes list of objects and a List of type then tried to validate the types of each object. I know following code is wrong but I just created to communicate the problem, it would be really great if you can tell me how can I do this in Scala.
object T extends App {
trait MyT
case class A(a: Int) extends MyT
case class B(b: Int) extends MyT
def validate(values: Seq[Any], types: Seq[MyT]): Seq[Boolean] =
for ((v, ty: MyT) ← values.zip(types)) yield v.isInstanceOf[ty]
// create objects
val a = A(1)
val b = B(1)
// call validate method
validate(Seq(a, b), Seq(A, B))
}
Types are not values, so you can't have a list of them.
But, you may use Class instead:
def validate(values: Seq[Any], types: Seq[Class[_ <: MyT]]): Seq[Boolean] =
values.lazyZip(types).map {
case (v, c) =>
c.isInstance(v)
}
Which can be used like this:
validate(Seq(a, b), Seq(classOf[A], classOf[B]))
// res: Seq[Boolean] = List(true, true)
You can see the code running here.
However, note that this will break if your classes start to have type parameters due to type erasure.
Note, I personally don't recommend this code since, IMHO, the very definition of validate is a code smell and I would bet you have a design error that lead to this situation.
I recommend searching for a way to avoid this situation and solve the root problem.
I am probably thinking about this the wrong way, but I am having trouble in Scala to use lenses on classes extending something with a constructor.
class A(c: Config) extends B(c) {
val x: String = doSomeProcessing(c, y) // y comes from B
}
I am trying to create a Lens to mutate this class, but am having trouble doing so. Here is what I would like to be able to do:
val l = Lens(
get = (_: A).x,
set = (c: A, xx: String) => c.copy(x = xx) // doesn't work because not a case class
)
I think it all boils down to finding a good way to mutate this class.
What are my options to achieve something like that? I was thinking about this in 2 ways:
Move the initialization logic into a companion A object into a def apply(c: Config), and change the A class to be a case class that gets created from the companion object. Unfortunately I can't extend from B(c) in my object because I only have access to c in its apply method.
Make x a var. Then in the Lens.set just A.clone then set the value of x then return the cloned instance. This would probably work but seems pretty ugly, not to mention changing this to a var might raise a few eyebrows.
Use some reflection magic to do the copy. Not really a fan of this approach if I can avoid it.
What do you think? Am I thinking about this really the wrong way, or is there an easy solution to this problem?
This depends on what you expect your Lens to do. A Lens laws specify that the setter should replace the value that the getter would get, while keeping everything else unchanged. It is unclear what is meant by everything else here.
Do you wish to have the constructor for B called when setting? Do you which the doSomeProcessing method called?
If all your methods are purely functional, then you may consider that the class A has two "fields", c: Config and x: String, so you might as well replace it with a case class with those fields. However, this will cause a problem while trying to implement the constructor with only c as parameter.
What I would consider is doing the following:
class A(val c: Config) extends B(c) {
val x = doSomeProcessing(c, y)
def copy(newX: String) = new A(c) { override val x = newX }
}
The Lens you wrote is now perfectly valid (except for the named parameter in the copy method).
Be careful if you have other properties in A which depend on x, this might create an instance with unexpected values for these.
If you do not wish c to be a property of class A, then you won't be able to clone it, or to rebuild an instance without giving a Config to your builder, which Lenses builder cannot have, so it seems your goal would be unachievable.
It's been a while since I've used constructors at all- so naturally when I have to use one in Scala I'm having trouble.
I want to do the following: When I create a new class without passing through anything- it creates an empty vector.
Otherwise if it passes through a vector- we use that vector and define it to be used with the class.
How do I do this? I previously had
Class example{
val a: Vector[int] = Vector();
Then I'm lost. I was thinking of doing something like
Class example{
val a: Vector[Int] = Vector()
def this(vector: Vector[Int]){
this{
a = vector
}
}
But I'm getting tons of errors. Can anyone help? I'm trying to find my scala book but I can't find it- I know it had a good section on constructors.
Sounds like you want a constructor with a default argument:
class example(val a : Vector[Int] = Vector())
If you really want to do this by constructor overloading, it looks like this:
class Example(val a: Vector[Int]) {
def this() = this(Vector())
}
Personal-opinion addendum: Overloading and default arguments are often good to avoid. I'd recommend just making a different function that calls the constructor:
class Example(val a: Vector[Int])
object Example {
def empty = new Example(Vector())
}
case class Example(a: Vector[Int] = Vector())
No need to put the val keyword.
Also, using the case keywork you get:
a compact initialisation syntax: Example(Vector(1,2)), instead of new Example(Vector(1,2))
pattern matching for you class
equality comparisons implicitly defined and pretty toString
Reference