- Published on
Class, Case Class, and their secrets!
- Authors
- Name
- Rodolfo Echemendía
Scala is a programming language that combines object-oriented and functional programming. In this article we will see the class and case class, how to use them, some secrets of the case class and why they are the favorites for everyday use.
What are Classes?
Like other programming languages, a class is a template that defines an object; can contain values, variables, types, and methods that mostly operate on them. In Scala, a class is defined with the keyword class and an identifier or name to describe it. Let's see an example:
class MyFirstClass
val x = new MyFirstClass
The language keyword new is used to create an instance of the class. MyFirstClass has a default constructor that doesn't take arguments. But we often want a constructor and a body in each class to give properties and behavior. Let's see another example with parameters constructor:
class Vehicle(
var passengers: Int,
var speed: Int,
val unit: String
) {
override def toString: String = s"(passengers: $passengers, speed: $speed $unit)"
}
val bicycle = new Vehicle(1, 30, "km")
bicycle.passengers // 1
bicycle.passengers = 2
bicycle.passengers // 2
println(bicycle) // (passengers: 2, speed: 30 km)
As we can see, this Vehicle class has 4 members: the variables passengers, speed and unit that can be edited, the method toString, unlike other languages, the main constructor is in the class signature and not after. builders like methods can also take a default value, define mutable or immutable variables and private or public as we will see below:
class Vehicle1(
var passengers: Int,
private val speed: Int,
private val unit: String = "km"
) {
val speedDescription: String = s"$speed $unit"
override def toString: String = s"(passengers: $passengers, speed: $speedDescription)"
}
object Vehicle1 {
def speed220km(passengers: Int): Vehicle1 = new Vehicle1(passengers, 220, "km")
}
val motorcycle = new Vehicle1(2, 100)
motorcycle.passengers // 2
motorcycle.speed // don't compile because the attribute is private
motorcycle.speedDescription // 100 km
println(motorcycle) // (passengers: 2, speed: 100 km)
val car: Vehicle1 = Vehicle1.speed220km(5)
println(car) // (passengers: 5, speed: 220 km)
In this last fragment, we can see a companion object where we can define other default constructors, such as the one to create a vehicle with 220 km of speed, also you can create an internal constructor that can also be overwritten, but we don't show any example because the truth is not used either as much as in other languages, in fact, many publications advise writing the most functional code possible, and for this, they recommend creating their class attributes like val or they also use case class which we will explain next.
What are case classes?
A case class is a class with all its features and more, when the Scala compiler sees the reserved word "case" in front of it. of each class generates multiple benefits such as:
- The constructor parameters are val and public by default, also are immutable, so accessor methods are generated for each of them.
- An apply method is automatically generated on the companion object that allows instantiating without using the new keyword.
- An unapply method is generated that allows you to use case classes in more ways in matching/pattern matching expressions.
- A beneficial copy method is generated and is used all the time in functional programming.
- In addition to equals, hashCode, and toString, allowing for better matching, use of map keys, terse typing, etc.
We will try to show all these characteristics below (starting and transforming the same example of the vehicle):
case class Vehicle2(passengers: Int, speed: Int, unit: String){
val speedDescription: String = s"$speed $unit"
}
We can already see a much cleaner and more concise class, we see how to instantiate objects in multiple ways:
// Normal constructor and the most used
val myCar = Vehicle2(5, 200, "km")
// Using apply explicitly
val myCar1 = Vehicle2.apply(5, 200, "km")
// By "tuple" of values
val myCar2 = Vehicle2.tupled((5, 200, "km"))
// Through currying mode parameters
val myCar3 = Vehicle2.curried(5)(200)("km")
We can use the automatically generated methods:
myCar.passengers // 5
myCar.speed // 200
myCar.speed = 300 // don't compile -> error: reassignment to val
myCar.unit // km
println(myCar) // Vehicle2(5,200,km)
val myFastCar = myCar.copy(passengers = 2, speed = 320)
println(myFastCar) // Vehicle2(2,320,km)
We compare by structure and not by reference:
myCar == myCar1 // true
myCar == myCar2 // true
myCar == myCar3 // true
myCar == myFastCar // false
We can use the unapply in match expressions (Simple Mode):
def recognizeVehicle(x: Vehicle2): String = x match {
case Vehicle2(10, speed, unit) =>
s"10 passenger minivan with speed of $speed $unit"
case Vehicle2(2, speed, _) if speed > 300 =>
s"High speed sports car ${x.speedDescription}"
case _ =>
"Any non-minivan or sports car: " + x
}
val minivan = Vehicle2(10, 100, "km")
println(recognizeVehicle(minivan))
// 10 passenger minivan with speed of 100 km
println(recognizeVehicle(myFastCar))
// High speed sports car 320 km
println(recognizeVehicle(myCar))
// Any non-minivan or sports car: Vehicle2(5,200,km)
Let's now look at a slightly more comprehensive example using unapply and pattern matching:
sealed trait Animal {
def name: String
}
case class Dog(name: String, owner: String) extends Animal
case class Cat(name: String, color: String) extends Animal
def recognizeAnimal(a: Animal): String = a match {
case Dog(name, owner) => s"The dog $name belongs to $owner."
case Cat(name, color) => s"${name.capitalize} is a very beautiful $color cat."
}
val bony = Dog("Bony", "Pedrito")
val tom = Cat("tom", "negro")
println(recognizeAnimal(bony))
// The dog Bony belongs to Pedrito.
println(recognizeAnimal(tom))
// Tom is a very beautiful black cat.
This works thanks to the Scala standard that an unapply method returns the constructor attributes of each case class in a tuple that is wrapped in an Option (We'll see in later articles what it means). This characteristic is considered according to Martin Odersky himself in his book Programming in Scala as the one with the greatest advantage of the case class, since Pattern matching is a fundamental feature in all functional programming languages.
References and other links of interest:
- Scala Lang(class): Classes
- Alvin Alexander / Scala: Case Classes
- Scala Exercises: Classes vs Case Classes
- Code Snippet: Scala Case Classes