Skip to content

Commit d7c7a2a

Browse files
committed
Reworked case class tutorial scala#547
1 parent 612d1cb commit d7c7a2a

File tree

2 files changed

+93
-48
lines changed

2 files changed

+93
-48
lines changed

.bundle/config

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
---
2-
BUNDLE_PATH: vendor/bundle
3-
BUNDLE_DISABLE_SHARED_GEMS: '1'
2+
BUNDLE_PATH: "vendor/bundle"
3+
BUNDLE_DISABLE_SHARED_GEMS: "true"

tutorials/tour/case-classes.md

Lines changed: 91 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,80 +10,125 @@ 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+
This is achieved through auto-generated code by the Scala compiler.
21+
22+
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`.
1623

1724
```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
25+
abstract class Notification
26+
case class Email(sourceEmail: String, title: String, body: String) extends Notification
27+
case class SMS(sourceNumber: String, message: String) extends Notification
28+
case class VoiceRecording(contactName: String, link: String) extends Notification
2229
```
2330

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

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

2839
```tut
29-
Fun("x", Fun("y", App(Var("x"), Var("y"))))
40+
val title = emailFromJohn.title
41+
println(title) // prints "Greetings From John!"
3042
```
3143

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

3447
```tut
35-
val x = Var("x")
36-
println(x.name)
48+
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.
49+
50+
val editedEmail = emailFromJohn.copy(title = "I am learning Scala!", body = "It's so cool!")
51+
52+
println(emailFromJohn) // prints "Email([email protected],Greetings From John!,Hello World!)"
53+
println(editedEmail) // prints "Email([email protected],I am learning Scala,It's so cool!)"
3754
```
3855

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

4158
```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))
59+
val firstSms = SMS("12345", "Hello!")
60+
val secondSms = SMS("12345", "Hello!")
61+
62+
if (firstSms == secondSms) {
63+
println("They are equal!")
64+
}
65+
66+
println("SMS is: " + firstSms)
4767
```
4868

4969
will print
5070

5171
```
52-
Var(x) == Var(x) => true
53-
Var(x) == Var(y) => false
72+
They are equal!
73+
SMS is: SMS(12345, Hello!)
5474
```
5575

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:
76+
With Case Classes, all the necessary machinery required for using them in **Pattern Matching** is automatically generated by the compiler.
77+
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+
_Case Classes_ is one of the biggest reasons why Scala code can be both concise and readable. With immutability and structural equality, working with
132+
case classes are just like working with primative values (Int, Double, etc), which in turn makes writing programs in a functional style much simpler
133+
and intuitive.
134+

0 commit comments

Comments
 (0)