简体   繁体   中英

What is the meaning of the word “case” in scala's “case class”?

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

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 Number s, namely using Zero and Succ constructors . Hence whenever we have to think about Number s, we at least know there are two different case s to consider. For example, say we want to define addition on Number s, 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.

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM