From ce7b008da70679926ef0bc6956469c528fa7d16a Mon Sep 17 00:00:00 2001 From: Travis Lee Date: Mon, 27 Feb 2017 16:43:18 +0100 Subject: [PATCH] Rewrote case classes tour --- .../tour/_posts/2017-02-13-case-classes.md | 144 ++++-------------- 1 file changed, 29 insertions(+), 115 deletions(-) diff --git a/tutorials/tour/_posts/2017-02-13-case-classes.md b/tutorials/tour/_posts/2017-02-13-case-classes.md index 2b1df15082..69998aa634 100644 --- a/tutorials/tour/_posts/2017-02-13-case-classes.md +++ b/tutorials/tour/_posts/2017-02-13-case-classes.md @@ -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("john.doe@mail.com", "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("guillaume@quebec.ca", "jorge@catalonia.es", "Ç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 guillaume@quebec.ca +message1.sender = "travis@washington.us" // 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(john.doe@mail.com,Greetings From John!,Hello World!)" -println(editedEmail) // prints "Email(john.doe@mail.com,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("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?") +val message3 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "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("julien@bretagne.fr", "travis@washington.us", "Me zo o komz gant ma amezeg") +val message5 = message4.copy(sender = message4.recipient, recipient = "claire@bourgogne.fr") +message5.sender // travis@washington.us +message5.recipient // claire@bourgogne.fr +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 = "jane@mail.com" -val someSms = SMS("12345", "Are you there?") -val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") -val specialEmail = Email("jane@mail.com", "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.