3

I understand that case class causes the compiler to augment a class with boilerplate to implement a certain useful pattern ("plain and immutable data-holding objects that should exclusively depend on their constructor arguments").

But the word "case" itself has no meaning to me in this context. I'm accustomed to its use as part of switch statements in C#, but that seems to have no relevance to Scala's use of the word.

I find it easier to program when I can attach words to particular meanings. Right now my mental model is case => boilerplate, in the same way that it could be blurg => boilerplate. It's a working mental model, but ambiguity makes it easy to misunderstand or to forget altogether.

So what does the word case have to do with what the compiler actually does?

I'm not looking for "what was in the mind of the designers" but rather "what's a rational way to relate this term to its meaning, given general knowledge of the language's design."

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
crenshaw-dev
  • 7,504
  • 3
  • 45
  • 81
  • 2
    Because it's major use case it to be out-of-the-box pattern-matched with `value match { case MyCaseClass(arg1, arg2) => ... }`. – Mateusz Kubuszok Aug 21 '19 at 14:39
  • 2
    The only peope who can objectively answer this would be the creators of the Language _(principally Prof. Martin Ordesky)_. But I believe the idea is that since case classes are meant to be **Pattern matched**, it makes sense to use `case` there. – Luis Miguel Mejía Suárez Aug 21 '19 at 14:39
  • @LuisMiguelMejíaSuárez fair. I rephrased the last line to be less opinion-y. I'm not looking for "what was in the mind of the designers" but rather "what's a rational way to relate this term to its meaning, given general knowledge of the language's design." – crenshaw-dev Aug 21 '19 at 14:42
  • 1
    @MichaelCrenshaw I guess you can either, use the word to be associated with **Pattern Matching** _(which is one of the main use cases of case classes)_, or you can just leave it as the word used to represent an immutable type-heterogeneous tuple of values _(formally know as a sum type, with is half the definition of an ADT)_. - If you think about it, there isn't really a reason for a class being named a class, is juts the name it was chosen _(This is partially false, as the word class was borrowed from mathematics, but nowadays OOP classes are really distant of the mathematical definition)_. – Luis Miguel Mejía Suárez Aug 21 '19 at 14:49
  • @LuisMiguelMejíaSuárez I think both the "pattern matching" and "mathematics" relationships help resolve the "blurg" problem - thinking of the terms as mere utterances. But I agree, `class` has taken on its own meaning. Maybe `case class` will eventually reach a usage level where the same happens. I'd be happy to accept an answer which explains the relationship to pattern matching! – crenshaw-dev Aug 21 '19 at 14:52
  • It's actually from the German word Käse. – som-snytt Aug 21 '19 at 23:25
  • @som-snytt if it’s not from the German region of Käse, is it just a sparkling tuple? – crenshaw-dev Aug 21 '19 at 23:30

2 Answers2

4

In my opinion, the term case comes from case analysis which is a reasoning technique enabled by special structures called algebraic data types. By itself case in case class might not make much sense, but when it forms a part of a sealed structure, which is how Scala defines ADTs, for example

sealed trait Number
case object Zero extends Number
case class Succ(v: Number) extends Number

then we see there are two forms of constructing Numbers, namely using Zero and Succ constructors. Hence whenever we have to think about Numbers, we at least know there are two different cases to consider. For example, say we want to define addition on Numbers, then its definition will have to handle two cases, perhaps, like so

def sum(a: Number, b: Number): Number =
  a match {
    case Zero => b
    case Succ(v) => Succ(sum(v, b))
  }

where

sum(Succ(Zero), Succ(Zero)) == Succ(Succ(Zero)) // 1 + 1 = 2
sum(Succ(Succ(Zero)), Succ(Zero)) == Succ(Succ(Succ(Zero))) // 2 + 1 = 3
sum(Succ(Zero), Succ(Succ(Zero))) == Succ(Succ(Succ(Zero))) // 1 + 2 = 3
sum(Zero, Succ(Succ(Zero))) == Succ(Succ(Zero)) // 0 + 2 = 2
sum(Succ(Succ(Zero)), Zero) == Succ(Succ(Zero)) // 2 + 0 = 2

Note how Scala in order to define ADT uses terms like class, object, trait etc., which appear to be from the object-oriented paradigm, however ADTs conceptually have little in common with class hierarchies found in OO. Personally I find this confusing, but we must remember Scala is both functional and OO language, which might be a reason for such terminological spillover. In some other more "pure" languages case class of ADT is represented simply by a vertical bar |, say like so

Inductive nat : Type :=
   | O : nat
   | S : nat -> nat.

My suggestion would be to try not to be a "slave to words" but instead words should serve you. What is important is the meaning behind the words or terms, not the words themselves. Do not build mental models around the terms, instead build mental models around the heavy concepts they are struggling to carry across feeble bridges of communication. In my opinion, the concept case is trying to communicate is that of ADT.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
  • 1
    I especially appreciate the analysis at the end. The heavy concepts are new to me, so I'm giving the feeble communicative bridge (the word `case`) quite the stress test. But I'll dig into case analysis and ADTs to try to work backwards to the word. Thanks so much! – crenshaw-dev Aug 21 '19 at 19:12
-1

C# has a switch / case language feature which allows controlling program flow by matching an input to a set of possible values.

public static GetEmail(string name)
{
  switch (name)
  {
    case "bill":
      return "bill@example.com";
    case "jane":
      return "jane@example.com";
    default:
      return null;
  }
}

Here, case roughly means "in the case that the given value is equal to this one, do X."

Scala's match / case feature is sort of like C#'s feature.

def getEmail(name: String): Option[String] = {
  name match {
    case "bill" => Option("bill@example.com")
    case "jane" => Option("jane@example.com")
    case _ => None
  }
}

But it's much more powerful. It is designed to evaluate "matches" against things farm more complex than strings. To take advantage of this powerful matching feature, you define immutable data-holding classes with case class.

Here is a trivial, but hopefully helpful, example of a case class and its use with match / case:

case class Person(name: String, hasEmail: Boolean)

object EmailHelper {
  def getEmail(person: Person): Option[String] = {
    person match {
      case Person(_, false) => None
      case Person("bill", true) => Option("bill@example.com")
      case Person("jane", true) => Option("jane@example.com")
      case _ => None
    }
  }
}

In short, case class enforces a set of constraints which make a class usable with match / case.

crenshaw-dev
  • 7,504
  • 3
  • 45
  • 81