Are constructor parameters treated differently in classes vs. case classes? - scala

In the book, "Programming in Scala 5th Edition", it is mentioned in the fourth chapter that we can create class' objects using the following way if a class is a case class:
scala> case class Person(name: String, age: Int)
// defined case class Person
scala> val p = Person("Sally", 39)
val p: Person = Person(Sally,39)
I do not find it different from the following normal way:
scala> class Person(name: String, age: Int)
// defined class Person
scala> val p = Person("Aviral", 24)
val p: Person = Person#7f5fcfe9
I tried accessing the objects in both the cases and there was a difference. When I declare the same class as a case class, I can access its members: p.name, p.age whereas if I try to do the same with a normally declared class, I get the following error:
1 |p.name
|^^^^^^
|value name cannot be accessed as a member of (p : Person) from module class rs$line$3$.
How are the two cases different as far as constructing an object is considered?

As the Tour Of Scala or the Scala 3 book says
When you create a case class with parameters, the parameters are public vals.
Therefore, in
case class Person(name: String, age: Int)
both name and age will be public, unlike in an ordinary class.

Related

How does case class in scala works internally

I have 1 question regarding case class of scala
scala> case class Person(name:String, age:Int)
scala> val person1 = Person("Posa",30)
scala> val person2 = new Person("Posa",30)
In what perspective both the objects (person1 and person2) differs ?
What difference does new keyword plays here?
When you create a case class
case class Person(name: String, age: Int)
compiler automatically creates a companion object which has a factory apply method
object Person {
def apply(name: String, age: Int): Person = new Person(name, age)
}
which is invoked when new is not used, so
val person1 = Person("Posa", 30) // function call creation syntax via companion's apply method
is equivalent to
val person1 = Person.apply("Posa", 30)
On the other hand instance creation expression is a constructor invocation on Person class (not its companion object)
val person2 = new Person("Posa", 30) // invokes constructor
Note starting Scala 3 even regular classes can be instantiated using function call creation syntax
Scala case classes generate apply methods, so that values of case
classes can be created using simple function application, without
needing to write new. Scala 3 generalizes this scheme to all concrete
classes.
Part of what the Scala compiler does (except in the case of an abstract case class) with a case class is that it generates a companion object for the class with an apply method with the same arguments as the case class's constructor and the case class as a result type.
When you write val person1 = Person("Posa", 30), that is exactly the same as writing
val person1 = Person.apply("Posa", 30)
The automatically generated apply method is effectively:
object Person {
def apply(name: String, age: Int): Person =
new Person(name, age)
}
Thus Person("Posa", 30) and new Person("Posa", 30) are the same thing.
For an abstract case class, the compiler will not generate an apply method in the companion object and will not generate a copy method in the case class, thus requiring an explicit new to construct an instance of a case class. This is not uncommonly used in an implementation pattern to ensure that:
only instances of a case class which satisfy domain constraints can be constructed
attempts to construct using apply will not throw an exception
For example, if we wanted to enforce that Persons must have a non-empty name and a non-negative age, we could
sealed abstract case class Person private[Person](name: String, age: Int) {
def copy(name: String = name, age: Int = age): Option[Person] =
Person(name, age)
}
object Person {
def apply(name: String, age: Int): Option[Person] =
if (name.nonEmpty && age >= 0) Some(unsafeApply(name, age))
else None
private[Person] def unsafeApply(name: String, age: Int) =
new Person(name, age) {}
}
In this, we've effectively renamed the compiler-generated apply to unsafeApply and made it private to the Person class and companion object (where we presumably can know that we're not violating a domain constraint, e.g. we could have a method in Person
def afterNextBirthday: Person = Person.unsafeApply(name, age + 1)
(since we're not changing the already validated name, and if we had a valid age (ignoring overflow...), adding 1 will not invalidate the age)). Our apply method now expresses that not all combinations of name and age are valid.

How to inherit class in scala [duplicate]

This question already has an answer here:
If case class inheritance is prohibited, how to represent this?
(1 answer)
Closed 5 years ago.
I am trying to inherit a class in Scala. My Parent class is
case class Person (name:String, age:Int, valid:Boolean)
My child class is
case class MarriedPerson (override val name: String,
override val age: Int,
override val valid: Boolean,
spouse: Person) extends Person(name, age, valid)
When I try this, I get an error saying
:13: error: case class MarriedPerson has case ancestor Person, but case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes.
Why is this the case and how I get around this to get a case class to inherit another case class?
If I remove the "case" in the parent class, I get an error saying
that
:15: error: value name overrides nothing
override val name: String,
Why can't a case class not inherit from a normal class in this case?
You cannot inherit from one case class to another. In theory its possible to inherit from a case class to a non-case class, but that's not recommended (which is why it's a good practice to make your case classes final - more on the topic in this post).
Case classes have, among other peculiarities, that of exposing publicly all of the constructor arguments. This does not happen for normal classes, which is why to make your snippet of code work you should just prepend all of Person's arguments with val.
Thus, the following is valid syntax:
class Person(val name: String, val age: Int, val valid: Boolean)
case class MarriedPerson(
override val name: String,
override val age: Int,
override val valid: Boolean,
spouse: Person) extends Person(name, age, valid)
However, for your information, it is also quite common to use traits for this kind of situations, which allows for greater flexibility (traits support multiple inheritance) and a slightly less verbose syntax, as follows:
trait Person {
def name: String
def age: Int
def valid: Boolean
}
case class MarriedPerson(
name: String,
age: Int,
valid: Boolean,
spouse: Person) extends Person
If you are curious on why it's possible to define defs in traits that are overridden by vals in sub-classes, it's because Scala implements the Uniform Access Principle.

Type aliasing a case class in Scala 2.10

I'm using Scala 2.10.2, and have two case classes that have identical fields:
case class Foo(id: String, name: String)
case class Bar(id: String, name: String)
I'd like to do something like this:
case class Thing(id: String, name: String)
type Foo = Thing
type Bar = Thing
This compiles, but when I try to create a Foo, I get:
scala> Bar("a", "b")
<console>:8: error: not found: value Bar
Bar("a", "b")
^
Does type aliasing not work with case classes?
When you create a case class Scala automatically creates a companion object for it. In your code you define an alias for the type Thing, i.e. for the class Thing only. Your companion object Thing still has only 1 name and no aliases.
One way to "fix" it is to create a reference to the companion object (not a type alias) like this:
scala> val Bar = Thing
Bar: Thing.type = Thing
scala> Bar("a", "b")
res1: Thing = Thing(a,b)
Another way to "fix" it would be to rename the imported object with import package.{Thing => Bar}.
Type aliases only alias the type, not any companion object that might be supplying factory methods (whether you write that factory method yourself or get one "for free" from the compiler).
On the other hand, importing acts on names and if there are multiple entities associated with a given name, importing that name brings in every referent of the imported name. Additionally, you can rename when importing and you can do so multiply, so...
scala> object Stuff { case class Thing(id: String, name: String) }
defined module Stuff
scala> import Stuff.Thing
import Stuff.Thing
scala> import Stuff.{Thing => Foo}
import Stuff.{Thing=>Foo}
scala> import Stuff.{Thing => Bar}
import Stuff.{Thing=>Bar}
scala> val thing1 = Thing("fing", "fang")
thing1: Stuff.Thing = Thing(fing,fang)
scala> val foo1 = Foo("yes", "no")
foo1: Stuff.Thing = Thing(yes,no)
scala> val bar1 = Bar("true", "false")
bar1: Stuff.Thing = Thing(true,false)
It's no good for the rendering via toString, though, as you can see.
case class Thing(id: String, name: String)
type Foo = Thing
type Bar = Thing
if you say new Bar("a","b") it will work
Do you want Foo and Bar to be distinguishable (as when they are different case classes) or not? If yes, and you just want to avoid repeating list of fields, you can do this:
case class Foo(thing: Thing)
case class Bar(thing: Thing)
But this will obviously make them a bit less convenient to use in pattern matching or access fields. Field access can be improved a bit:
trait ThingWrapper {
def thing: Thing
def id = thing.id
def name = thing.name
}
case class Foo(thing: Thing) extends ThingWrapper
case class Bar(thing: Thing) extends ThingWrapper

Scala List and Subtypes

I want to be able to refer to a list that contains subtypes and pull elements from that list and have them implicitly casted. Example follows:
scala> sealed trait Person { def id: String }
defined trait Person
scala> case class Employee(id: String, name: String) extends Person
defined class Employee
scala> case class Student(id: String, name: String, age: Int) extends Person
defined class Student
scala> val x: List[Person] = List(Employee("1", "Jon"), Student("2", "Jack", 23))
x: List[Person] = List(Employee(1,Jon), Student(2,Jack,23))
scala> x(0).name
<console>:14: error: value name is not a member of Person
x(0).name
^
I know that x(0).asInstanceOf[Employee].name but I was hoping there was a more elegant way with types. Thanks in advance.
The best way is to use pattern matching. Because you are using a sealed trait the match will be exhaustive which is nice.
x(0) match {
case Employee(id, name) => ...
case Student(id, name, age) => ...
}
Well, if you want the employees, you could always use a collect:
val employees = x collect { case employee: Employee => employee }

lift-json serialization of case class hierarchy

I have a hierarchy like the following:
case class A(val a: Long, val b: String)
case class B(val c: String) extends A(a=3, b="a string")
and I'm trying to serialize it using lift-json ala the following:
val obj = B(c="another string")
val cameraJson = net.liftweb.json.Serialization.write(obj)
but what I'm seeing is that it only serializes the properties in class B and not those in A.
I've also tried:
compact(render(decompose(obj)))
with the same result
What gives? Is there something obvious in Scala that I'm missing?
case class inheritance is a deprecated feature of Scala. This should work for instance:
trait A { val a: Long; val b: String }
case class B(a: Long = 3, b: String = "a string", c: String) extends A
val obj = B(c="another string")
var ser = Serialization.write(obj)
Serialization.read[B](ser)
Classic lift JSON serialisation for case classes is based on constructor argument list (see decompose implementation), not class attributes. So you have to either override all fields declared in the parent trait (as in the #Joni answer) or use composition instead of inheritance.
For example:
case class A(a: Long, b: String)
case class B(c: String, a: A = A(a=3, b="a string"))
B(c="another string")
BTW val keyword in case class constructor is unnecessary. The accessors for every constructor arg is one of the things you are getting for free by declaring class as a case.
IMO serializing only c seems like the right thing to do. The info appearing in serialization will be that the type is B and the value of C. Values of a and b are implied by the info type is B. They are 3 and "a string", and can be nothing else, so they do not need to be written.
From maybe too cursory a look at the source code, the default behavior of is to serialize fields that correspond to parameters of the primary constructor