Skip to content

Reworked case class tutorial #547 #599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 18, 2016
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 91 additions & 46 deletions tutorials/tour/case-classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,80 +10,125 @@ tutorial-next: pattern-matching
tutorial-previous: currying
---

Scala supports the notion of _case classes_. Case classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via [pattern matching](pattern-matching.html).
Scala supports the notion of _case classes_. Case classes are just regular classes that are:

Here is an example for a class hierarchy which consists of an abstract super class `Term` and three concrete case classes `Var`, `Fun`, and `App`.
* Immutable by default
* Decomposable through [Pattern Matching](pattern-matching.html)
* Compared by structural equality instead of by reference
* Succinct to instantiate and operate on

This is achieved through auto-generated code by the Scala compiler.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below


Here is an example for a Notification type hierarchy which consists of an abstract super class `Notification` and three concrete Notification types implemented with case classes `Email`, `SMS`, and `VoiceRecording`.

```tut
abstract class Term
case class Var(name: String) extends Term
case class Fun(arg: String, body: Term) extends Term
case class App(f: Term, v: Term) extends Term
abstract class Notification
case class Email(sourceEmail: String, title: String, body: String) extends Notification
case class SMS(sourceNumber: String, message: String) extends Notification
case class VoiceRecording(contactName: String, link: String) extends Notification
```

This class hierarchy can be used to represent terms of the [untyped lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus). To facilitate the construction of case class instances, Scala does not require that the `new` primitive is used. One can simply use the class name as a function.
Instantiating a Case class is easy: (Note that we don't need to use the `new` keyword)

```tut
val emailFromJohn = Email("[email protected]", "Greetings From John!", "Hello World!")
```

Here is an example:
The constructor parameters of case classes are treated as public values and can be accessed directly.

```tut
Fun("x", Fun("y", App(Var("x"), Var("y"))))
val title = emailFromJohn.title
println(title) // prints "Greetings From John!"
```

The constructor parameters of case classes are treated as public values and can be accessed directly.
With case classes, you cannot mutate their fields directly. Instead, you make a copy using the `copy` method.
As seen below, you can replace just some of the fields:

```tut
val x = Var("x")
println(x.name)
emailFromJohn.title = "Goodbye From John!" // This is a compilation error. We cannot assign another value to val fields, which all case classes fields are by default.

val editedEmail = emailFromJohn.copy(title = "I am learning Scala!", body = "It's so cool!")

println(emailFromJohn) // prints "Email([email protected],Greetings From John!,Hello World!)"
println(editedEmail) // prints "Email([email protected],I am learning Scala,It's so cool!)"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this code block is designed to fail then tut should be tut:fail. This will allow tut to give an error if the code block compiles. It is a little weird.

https://github.com/tpolecat/tut

Also check the Travis logs if they fail. They will show you where to look to fix the problem. :-)

https://travis-ci.org/scala/scala.github.com/builds/166207587

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll fix this up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jarrodwb I have fixed this a TravisCI run is passing.

```

For every case class the Scala compiler generates an `equals` method which implements structural equality and a `toString` method. For instance:

```tut
val x1 = Var("x")
val x2 = Var("x")
val y1 = Var("y")
println("" + x1 + " == " + x2 + " => " + (x1 == x2))
println("" + x1 + " == " + y1 + " => " + (x1 == y1))
val firstSms = SMS("12345", "Hello!")
val secondSms = SMS("12345", "Hello!")

if (firstSms == secondSms) {
println("They are equal!")
}

println("SMS is: " + firstSms)
```

will print

```
Var(x) == Var(x) => true
Var(x) == Var(y) => false
They are equal!
SMS is: SMS(12345, Hello!)
```

It only makes sense to define case classes if pattern matching is used to decompose data structures. The following [object](singleton-objects.html) defines a pretty printer function for our lambda calculus representation:
With Case Classes, all the necessary machinery required for using them in **Pattern Matching** is automatically generated by the compiler.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering; is it really necessary to point out that there's stuff generated by the compiler? Just thinking that mentions of this could be removed here, as this sort of information is generally not required to use/understand case classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think I put that in because it was really useful to know that pattern matching isn't some magical thing Scala did which only applies to case classes (coming from a Java-ish background). However you are correct - if they read this tutorial on case classes before pattern matching (which is the right order, imo), then the implementation detail of pattern matching doesn't need to be mentioned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been removed

Here's a function that prints out different messages depending on what type of Notification is received:

```tut
object TermTest extends scala.App {
def printTerm(term: Term) {
term match {
case Var(n) =>
print(n)
case Fun(x, b) =>
print("^" + x + ".")
printTerm(b)
case App(f, v) =>
print("(")
printTerm(f)
print(" ")
printTerm(v)
print(")")
}
def showNotification(notification: Notification): String = {
notification match {
case Email(email, title, _) =>
"You got an email from " + email + " with title: " + title
case SMS(number, message) =>
"You got an SMS from " + number + "! Message: " + message
case VoiceRecording(name, link) =>
"you received a Voice Recording from " + name + "! Click the link to hear it: " + link
}
def isIdentityFun(term: Term): Boolean = term match {
case Fun(x, Var(y)) if x == y => true
case _ => false
}

val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")

println(showNotification(someSms))
println(showNotification(someVoiceRecording))

// prints:
// You got an SMS from 12345! Message: Are you there?
// you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
```

Here's a more involved example using `if` guards. With the `if` guard, the pattern match branch will fail if the condition in the guard returns false.

```tut
def showNotificationSpecial(notification: Notification, specialEmail: String, specialNumber: String): String = {
notification match {
case Email(email, _, _) if email == specialEmail =>
"You got an email from special someone!"
case SMS(number, _) if number == specialNumber =>
"You got an SMS from special someone!"
case other =>
showNotification(other) // nothing special, delegate to our original showNotification function
}
val id = Fun("x", Var("x"))
val t = Fun("x", Fun("y", App(Var("x"), Var("y"))))
printTerm(t)
println
println(isIdentityFun(id))
println(isIdentityFun(t))
}

val SPECIAL_NUMBER = "55555"
val SPECIAL_EMAIL = "[email protected]"
val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
val specialEmail = Email("[email protected]", "Drinks tonight?", "I'm free after 5!")
val specialSms = SMS("55555", "I'm here! Where are you?")

// prints:
// You got an SMS from 12345! Message: Are you there?
// you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
// You got an email from special someone!
// You got an SMS from special someone!

```

In our example, the function `printTerm` is expressed as a pattern matching statement starting with the `match` keyword and consisting of sequences of `case Pattern => Body` clauses.
The program above also defines a function `isIdentityFun` which checks if a given term corresponds to a simple identity function. This example uses deep patterns and guards. After matching a pattern with a given value, the guard (defined after the keyword `if`) is evaluated. If it returns `true`, the match succeeds; otherwise, it fails and the next pattern will be tried.
_Case Classes_ is one of the biggest reasons why Scala code can be both concise and readable. With immutability and structural equality, working with
case classes are just like working with primative values (Int, Double, etc), which in turn makes writing programs in a functional style much simpler
and intuitive.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about these last two sentences; not sure what they add, seems a bit fluffy. Maybe remove these?

Copy link
Contributor Author

@jatcwang jatcwang Oct 13, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that they are a little bit fluffy. They were put in because I wanted to stress that

  • They can be used pretty much just like values due to immutability and comparison-by-value
  • They should be your goto when you are modeling data classes

If you think by this point a new reader will have these point driven home then I can remove them. Or perhaps reword them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Said this way, a much stronger point emerges. Care to try rewording?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Let me know what you think!