Skip to content

Commit 786e3d1

Browse files
authored
Merge pull request #599 from jtcwang/master
Reworked case class tutorial #547
2 parents 4ae4849 + aee907b commit 786e3d1

File tree

1 file changed

+94
-46
lines changed

1 file changed

+94
-46
lines changed

Diff for: tutorials/tour/case-classes.md

+94-46
Original file line numberDiff line numberDiff line change
@@ -10,80 +10,128 @@ tutorial-next: pattern-matching
1010
tutorial-previous: currying
1111
---
1212

13-
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).
13+
Scala supports the notion of _case classes_. Case classes are just regular classes that are:
1414

15-
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`.
15+
* Immutable by default
16+
* Decomposable through [pattern matching](pattern-matching.html)
17+
* Compared by structural equality instead of by reference
18+
* Succinct to instantiate and operate on
19+
20+
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`.
1621

1722
```tut
18-
abstract class Term
19-
case class Var(name: String) extends Term
20-
case class Fun(arg: String, body: Term) extends Term
21-
case class App(f: Term, v: Term) extends Term
23+
abstract class Notification
24+
case class Email(sourceEmail: String, title: String, body: String) extends Notification
25+
case class SMS(sourceNumber: String, message: String) extends Notification
26+
case class VoiceRecording(contactName: String, link: String) extends Notification
2227
```
2328

24-
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.
29+
Instantiating a case class is easy: (Note that we don't need to use the `new` keyword)
30+
31+
```tut
32+
val emailFromJohn = Email("[email protected]", "Greetings From John!", "Hello World!")
33+
```
2534

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

2837
```tut
29-
Fun("x", Fun("y", App(Var("x"), Var("y"))))
38+
val title = emailFromJohn.title
39+
println(title) // prints "Greetings From John!"
40+
```
41+
42+
With case classes, you cannot mutate their fields directly. (unless you insert `var` before a field, but doing so is generally discouraged).
43+
44+
```tut:fail
45+
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.
3046
```
3147

32-
The constructor parameters of case classes are treated as public values and can be accessed directly.
48+
Instead, you make a copy using the `copy` method. As seen below, you can replace just some of the fields:
3349

3450
```tut
35-
val x = Var("x")
36-
println(x.name)
51+
val editedEmail = emailFromJohn.copy(title = "I am learning Scala!", body = "It's so cool!")
52+
53+
println(emailFromJohn) // prints "Email([email protected],Greetings From John!,Hello World!)"
54+
println(editedEmail) // prints "Email([email protected],I am learning Scala,It's so cool!)"
3755
```
3856

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

4159
```tut
42-
val x1 = Var("x")
43-
val x2 = Var("x")
44-
val y1 = Var("y")
45-
println("" + x1 + " == " + x2 + " => " + (x1 == x2))
46-
println("" + x1 + " == " + y1 + " => " + (x1 == y1))
60+
val firstSms = SMS("12345", "Hello!")
61+
val secondSms = SMS("12345", "Hello!")
62+
63+
if (firstSms == secondSms) {
64+
println("They are equal!")
65+
}
66+
67+
println("SMS is: " + firstSms)
4768
```
4869

4970
will print
5071

5172
```
52-
Var(x) == Var(x) => true
53-
Var(x) == Var(y) => false
73+
They are equal!
74+
SMS is: SMS(12345, Hello!)
5475
```
5576

56-
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:
77+
With case classes, you can utilize **pattern matching** to work with your data. Here's a function that prints out different messages depending on what type of Notification is received:
5778

5879
```tut
59-
object TermTest extends scala.App {
60-
def printTerm(term: Term) {
61-
term match {
62-
case Var(n) =>
63-
print(n)
64-
case Fun(x, b) =>
65-
print("^" + x + ".")
66-
printTerm(b)
67-
case App(f, v) =>
68-
print("(")
69-
printTerm(f)
70-
print(" ")
71-
printTerm(v)
72-
print(")")
73-
}
80+
def showNotification(notification: Notification): String = {
81+
notification match {
82+
case Email(email, title, _) =>
83+
"You got an email from " + email + " with title: " + title
84+
case SMS(number, message) =>
85+
"You got an SMS from " + number + "! Message: " + message
86+
case VoiceRecording(name, link) =>
87+
"you received a Voice Recording from " + name + "! Click the link to hear it: " + link
7488
}
75-
def isIdentityFun(term: Term): Boolean = term match {
76-
case Fun(x, Var(y)) if x == y => true
77-
case _ => false
89+
}
90+
91+
val someSms = SMS("12345", "Are you there?")
92+
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
93+
94+
println(showNotification(someSms))
95+
println(showNotification(someVoiceRecording))
96+
97+
// prints:
98+
// You got an SMS from 12345! Message: Are you there?
99+
// you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
100+
```
101+
102+
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.
103+
104+
```tut
105+
def showNotificationSpecial(notification: Notification, specialEmail: String, specialNumber: String): String = {
106+
notification match {
107+
case Email(email, _, _) if email == specialEmail =>
108+
"You got an email from special someone!"
109+
case SMS(number, _) if number == specialNumber =>
110+
"You got an SMS from special someone!"
111+
case other =>
112+
showNotification(other) // nothing special, delegate to our original showNotification function
78113
}
79-
val id = Fun("x", Var("x"))
80-
val t = Fun("x", Fun("y", App(Var("x"), Var("y"))))
81-
printTerm(t)
82-
println
83-
println(isIdentityFun(id))
84-
println(isIdentityFun(t))
85114
}
115+
116+
val SPECIAL_NUMBER = "55555"
117+
val SPECIAL_EMAIL = "[email protected]"
118+
val someSms = SMS("12345", "Are you there?")
119+
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
120+
val specialEmail = Email("[email protected]", "Drinks tonight?", "I'm free after 5!")
121+
val specialSms = SMS("55555", "I'm here! Where are you?")
122+
123+
// prints:
124+
// You got an SMS from 12345! Message: Are you there?
125+
// you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
126+
// You got an email from special someone!
127+
// You got an SMS from special someone!
128+
86129
```
87130

88-
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.
89-
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.
131+
When programming in Scala, it is recommended that you use case classes pervasively to model/group data as they help you to write more expressive and maintainable code:
132+
133+
* Immutability frees you from needing to keep track of where and when things are mutated
134+
* Comparison-by-value allows you compare instances as if they are primitive values - no more uncertainty regarding whether instances of a class is compared by value or reference
135+
* Pattern matching simplifies branching logic, which leads to less bugs and more readable code.
136+
137+

0 commit comments

Comments
 (0)