A functional Journey

by Rewati Raman
HTML5 Icon

Case Classes

14 Feb 2017

Case classes are Scala's way to allow pattern matching on objects without requiring a large amount of boilerplate. Generally, all you need to do is add a single case keyword as modifier to each class that you want to be pattern matchable. Classes with such a modifier are called case classes. Using the modifier makes the Scala compiler add some syntactic conveniences to your class.

Features added to case classes by compiler
  • A factory method with the name of the class. So you don't have to use new keyword to create an instance.
  • Implements the apply method that you can use as a factory.
  • All arguments in the parameter list of a case class implicitly are val or immutable.
  • Compiler adds "natural" implementations of method toString.
  • Compiler adds "natural" implementations of method hashCode.
  • Compiler adds "natural" implementations of method equals. Which enable comparing a whole tree consisting of the class and (recursively) all its arguments. Since == in Scala always delegates to equals, this means that elements of case classes are always compared structurally.
  • Compiler adds "natural" implementations of method copy. This method is useful for making a new instance of the class that is the same as another one except that one or two attributes are different.
  • Compiler implements the unapply method. This helps in supporting pattern matching. This is especially important when you define an Algebraic Data Type. It also helps in deconstructing a case class like:
      scala> case class Point(x: Int, y: Int)
      defined class Point
      scala> val p = Point(22,23)
      p: Point = Point(22,23)
      scala> val Point(x,y) = p
      x: Int = 22
      y: Int = 23
      scala> println(x+y)
Immutable by default

As stated above all the fields of the case class are val by default, you cannot mutate their fields directly, unless you insert var before a field.

Decomposable through pattern matching

Case classes provide a recursive decomposition mechanism via pattern matching.

      scala> val p1 = Point(22, 23)
      p1: Point = Point(22,23)
      scala> def a(a: Any) =
           | a match {
           | case Point(x,y) => println(s"x is: $x, y is $y")
           | case _ => println("This is not a point")
           | }
      a: (a: Any)Unit

      scala> a(p1)
      x is: 22, y is 23

      scala> a("not a point")
      This is not a point
Compared by structural equality instead of by reference
  scala> val p1 = Point(22, 23)
  p1: Point = Point(22,23)

  scala> val p2 = Point(22, 23)
  p2: Point = Point(22,23)

  scala> p1 == p2
  res5: Boolean = true
Extends Product trait

Case class extends Product trait. Some of these method can save so much of coding and makes our data model very expressive.

ProductArity method returns the size of this product.

        scala> val p = Point(22, 23)
        p: Point = Point(22,23)

        scala> val arity = p.productArity
        arity: Int = 2

ProductElement(n: Int) returns Any returns the n-th element of this product. It starts from 0.

        scala> println(p.productElement(0))

ProductIterator: Iterator[Any] returns an iterator over all the elements of case class.

ProductPrefix: String, returns a string used in the toString method of the derived classes.

  scala> p.productPrefix
  res2: String = Point