-
Notifications
You must be signed in to change notification settings - Fork 1k
Rewrote case classes tour #715
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
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,135 +9,49 @@ categories: tour | |
num: 11 | ||
next-page: pattern-matching | ||
previous-page: currying | ||
prerequisite-knowledge: classes, basics, mutability | ||
--- | ||
|
||
Scala supports the notion of _case classes_. Case classes are just regular classes that are: | ||
|
||
* Immutable by default | ||
* Decomposable through [pattern matching](pattern-matching.html) | ||
* Compared by structural equality instead of by reference | ||
* Succinct to instantiate and operate on | ||
|
||
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`. | ||
Case classes are like regular classes with a few key differences which we will go over. Case classes are good for modeling immutable data. In the next step of the tour, we'll see how they are useful in [pattern matching](pattern-matching.html). | ||
|
||
## Defining a case class | ||
A minimal case class requires the keywords `case class`, an identifier, and a parameter list (which may be empty): | ||
```tut | ||
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 | ||
``` | ||
case class Book(isbn: String) | ||
|
||
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!") | ||
val frankenstein = Book("978-0486282114") | ||
``` | ||
Notice how the keyword `new` was not used to instantiate the `Message` case class. This is because case classes have an `apply` method by default which takes care of object construction. | ||
|
||
The constructor parameters of case classes are treated as public values and can be accessed directly. | ||
|
||
```tut | ||
val title = emailFromJohn.title | ||
println(title) // prints "Greetings From John!" | ||
When you create a case class with parameters, the parameters are public `val`s. | ||
``` | ||
case class Message(sender: String, recipient: String, body: String) | ||
val message1 = Message("[email protected]", "[email protected]", "Ça va ?") | ||
|
||
With case classes, you cannot mutate their fields directly. (unless you insert `var` before a field, but doing so is generally discouraged). | ||
|
||
```tut:fail | ||
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. | ||
println(message1.sender) // prints [email protected] | ||
message1.sender = "[email protected]" // this line does not compile | ||
``` | ||
You can't reassign `message1.sender` because it is a `val` (i.e. immutable). It is possible to use `var`s in case classes but this is discouraged. | ||
|
||
Instead, you make a copy using the `copy` method. As seen below, you can replace just some of the fields: | ||
|
||
```tut | ||
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!)" | ||
## Comparison | ||
Case classes are compared by structure and not by reference: | ||
``` | ||
case class Message(sender: String, recipient: String, body: String) | ||
|
||
For every case class the Scala compiler generates an `equals` method which implements structural equality and a `toString` method. For instance: | ||
|
||
```tut | ||
val firstSms = SMS("12345", "Hello!") | ||
val secondSms = SMS("12345", "Hello!") | ||
|
||
if (firstSms == secondSms) { | ||
println("They are equal!") | ||
} | ||
|
||
println("SMS is: " + firstSms) | ||
val message2 = Message("[email protected]", "[email protected]", "Com va?") | ||
val message3 = Message("[email protected]", "[email protected]", "Com va?") | ||
val messagesAreTheSame = message2 == message3 // true | ||
``` | ||
Even though `message2` and `message3` refer to different objects, the value of each object is equal. | ||
|
||
will print | ||
|
||
## Copying | ||
You can create a deep copy of an instance of a case class simply by using the `copy` method. You can optionally change the constructor arguments. | ||
``` | ||
They are equal! | ||
SMS is: SMS(12345, Hello!) | ||
case class Message(sender: String, recipient: String, body: String) | ||
val message4 = Message("[email protected]", "[email protected]", "Me zo o komz gant ma amezeg") | ||
val message5 = message4.copy(sender = message4.recipient, recipient = "[email protected]") | ||
message5.sender // [email protected] | ||
message5.recipient // [email protected] | ||
message5.body // "Me zo o komz gant ma amezeg" | ||
``` | ||
|
||
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: | ||
|
||
```tut | ||
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 | ||
} | ||
} | ||
|
||
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 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?") | ||
|
||
println(showNotificationSpecial(someSms, SPECIAL_EMAIL, SPECIAL_NUMBER)) | ||
println(showNotificationSpecial(someVoiceRecording, SPECIAL_EMAIL, SPECIAL_NUMBER)) | ||
println(showNotificationSpecial(specialEmail, SPECIAL_EMAIL, SPECIAL_NUMBER)) | ||
println(showNotificationSpecial(specialSms, SPECIAL_EMAIL, SPECIAL_NUMBER)) | ||
|
||
// 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! | ||
|
||
``` | ||
|
||
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: | ||
|
||
* Immutability frees you from needing to keep track of where and when things are mutated | ||
* 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 | ||
* Pattern matching simplifies branching logic, which leads to less bugs and more readable code. | ||
|
||
|
||
The recipient of `message4` is used as the sender of `message5` but the `body` of `message4` was copied directly. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These examples are excellent!