Why does scala allow private case class fields? - scala

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

Related

Scala secondary constructor "Application does not take parameters" error [duplicate]

I think I see the merit in defining auxiliary constructors in such a way that the primary constructor is the solitary point of entry to the class. But why can't I do something like this?
class Wibble(foo: Int, bar: String) {
def this(baz: List[Any]) = {
val bazLength = baz.length
val someText = "baz length is " ++ bazLength.toString
this(bazLength, someText)
}
}
Is it maybe a way of guaranteeing that the auxiliary constructor doesn't have side effects and/or can't return early?
Auxiliary constructors can contain more than a single invocation of another constructor, but their first statement must be said invocation.
As explained in Programming in Scala, ch. 6.7:
In Scala, every auxiliary constructor must invoke another constructor of
the same class as its first action. In other words, the first statement in every
auxiliary constructor in every Scala class will have the form this(. . . ).
The invoked constructor is either the primary constructor (as in the Rational
example), or another auxiliary constructor that comes textually before the
calling constructor. The net effect of this rule is that every constructor invocation
in Scala will end up eventually calling the primary constructor of the
class. The primary constructor is thus the single point of entry of a class.
If you’re familiar with Java, you may wonder why Scala’s rules for
constructors are a bit more restrictive than Java’s. In Java, a constructor
must either invoke another constructor of the same class, or directly invoke
a constructor of the superclass, as its first action. In a Scala class, only the
primary constructor can invoke a superclass constructor. The increased
restriction in Scala is really a design trade-off that needed to be paid in
exchange for the greater conciseness and simplicity of Scala’s constructors
compared to Java’s.
Just as in Java, one can get round this limitation by extracting the code to be executed before the primary constructor call into a separate method. In Scala it is a bit more tricky than in Java, as apparently you need to move this helper method into the companion object in order to be allowed to call it from the constructor.
Moreover, your specific case is awkward as you have two constructor parameters and - although one can return tuples from a function - this returned tuple is then not accepted as the argument list to the primary constructor. For ordinary functions, you can use tupled, but alas, this doesn't seem to work for constructors. A workaround would be to add yet another auxiliary constructor:
object Wibble {
private def init(baz: List[Any]): (Int, String) = {
val bazLength = baz.length
val someText = "baz length is " ++ bazLength.toString
println("init")
(bazLength, someText)
}
}
class Wibble(foo: Int, bar: String) {
println("Wibble wobble")
def this(t: (Int, String)) = {
this(t._1, t._2)
println("You can execute more code here")
}
def this(baz: List[Any]) = {
this(Wibble.init(baz))
println("You can also execute some code here")
}
}
This at least works, even if it is slightly complicated.
scala> val w = new Wibble(List(1, 2, 3))
init
Wibble wobble
You can execute more code here
You can also execute some code here
w: Wibble = Wibble#b6e385
Update
As #sschaef's pointed out in his comment, this can be simplified using a factory method in the companion object:
object Wobble {
def apply(baz: List[Any]): Wobble = {
val bazLength = baz.length
val someText = "baz length is " ++ bazLength.toString
println("init")
new Wobble(bazLength, someText)
}
}
class Wobble(foo: Int, bar: String) {
println("Wobble wibble")
}
Thus we need no new to create an object anymore:
scala> val w = Wobble(List(1, 2, 3))
init
Wobble wibble
w: Wobble = Wobble#47c130

Can I customise the value components in a case class?

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!

I need a specific example of how to define a local parameter in the primary constructor of an immutable _case_ class

I have normal Scala class I am wanting to refactor to become an immutable case class. As I'm needing the class to be well-behaved in Set operations, I want all the Scala compiler automatically generated methods provided on a case class. IOW, I am wanting to avoid having to write these various methods; equals, hashCode, toString, etc., as that is very error prone. And I am needing to do this for a raft of classes, so I need a general solution, not just a specific solution anomalous quick fix or hack.
Here's the class with which I am working:
class Node(val identity: String, childrenArg: List[Node], customNodeArg: CustomNode) {
val children: List[Node] = childrenArg
val customNode: CustomNode = customNodeArg
}
As you can see, the class's constructor has three parameters. The first one, identity, is a read-only property. The remaining two, childrenArg and customNodeArg, are just a normal method parameters; i.e. they are only present during the construction of the instance and then disappears altogether from the class instance (unless otherwise captured) upon execution completion of the class constructor.
My first naive attempt to convert this to an immutable case class was this (just removing val from the first parameter):
class Node(identity: String, childrenArg: List[Node], customNodeArg: CustomNode) {
val children: List[Node] = childrenArg
val customNode: CustomNode = customNodeArg
}
However, this resulted in the undesired effect of both the childrenArg and customNodeArg parameters now being elevated to become (read-only) properties (as opposed to leaving them as normal method parameters). And this had the further undesired effect of having them included in the compiler generated equals and hashCode implementations.
How do I mark the immutable case class's constructor parameters childrenArg and customNodeArg such that identity is the only read-only property of the case class?
Any guidance on this; answers, website discussion links, etc., are greatly appreciated.
A second parameter list seems to do the trick:
scala> trait CustomNode
defined trait CustomNode
scala> case class Node(identity: String)(childrenArg: List[Node], customNodeArg: CustomNode)
defined class Node
scala> val n = Node("id")(Nil, null)
n: Node = Node(id)
scala> n.identity
res0: String = id
scala> n.getClass.getDeclaredFields.map(_.getName)
res1: Array[String] = Array(identity)
Case class parameters are vals by default, but you can set them to vars.
case class Node(identity: String, var childrenArg: List[Node], var customNodeArg: CustomNode)
Making them vars gives you getters and setters automatically

Trouble with constructors in Scala

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

How do I get Scala's Option class so I can pass it to getDeclaredMethod()

I'm trying to get the classOf[the-abstract-class-Option], but instead I always get the classOf[the-Option-*object*]. How can I get the class of the abstract class instead?
Both Option.getClass and classOf[Option[_]] gives me class scala.Option$.
Edit: I needn't have asked this; all of a sudden, classOf[Option[_]] works fine, weird. /Edit
Background:
I'm trying to invoke via reflection a method that takes an Option[String] parameter.
It signature look like so: ...(..., anySectionId: Option[String], ...)...
Before I can invoke the method, I look it up via getDeclaredMethod. But to do that, I need a list of parameter types, which I construct by calling _.getClass on each argument I'm going to give to the method. But _.getClass returns classOf[None] or classOf[Some] for Option instances, which makes getDeclaredMethod fail, because (?) the signature is based on Option not Some/None.
Here's the code:
val clazz: Class[_] = Play.current.classloader.loadClass(className)
val paramTypes = arguments.map(_ match {
case None => Option.getClass // gives me the object, not the abstract class
case _: Some[_] => classOf[Option[_]] // this also gives me the object :-(
case x => x.getClass // results in None or Some for Option instances
})
val m: jl.reflect.Method = clazz.getDeclaredMethod("apply", paramTypes: _*)
and the last line above fails for a method with any Option parameter (otherwise everything works fine).
The best way is use Scala reflection.
The next best way is not to make work for yourself by trying to match the param types.
Using getClass fails for subtypes:
scala> class Foo
defined class Foo
scala> class Bar extends Foo
defined class Bar
scala> class Baz { def baz(f: Foo) = 1 }
defined class Baz
scala> val b = new Baz
b: Baz = Baz#d33eaa9
scala> val p = new Bar
p: Bar = Bar#406c5ca2
scala> classOf[Baz].getDeclaredMethod("baz", p.getClass)
java.lang.NoSuchMethodException: Baz.baz(Bar)
It's easier just to match on the name:
scala> classOf[Baz].getMethods.find(_.getName == "baz") map (_.invoke(b,p)) getOrElse -1
res5: Any = 1
or filter on the number of params for poor man's overloading resolution, then maybe filter on all args having conforming types.
The notation for accidentally getting the object is in fact:
scala> classOf[Option$]
res8: Class[Option$] = class scala.Option$
Answer: classOf[Option[_]]
Weird! Suddenly classOf[Option[_]] works. I feel sure I tested once or twice before I submitted the question :-( Perhaps the IDE didn't have time to save the file before I recompiled, weird.
I don't know if I should delete the question. Or perhaps I should leave it as is, in case classOf[Option[_]] isn't obvious to everyone.