From 94fad22ee14f20174347b50c2dfc55869d6fc6dc Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Mon, 10 Oct 2016 00:00:59 +1000 Subject: [PATCH 1/7] Reworked case class tutorial #547 --- tutorials/tour/case-classes.md | 137 ++++++++++++++++++++++----------- 1 file changed, 91 insertions(+), 46 deletions(-) diff --git a/tutorials/tour/case-classes.md b/tutorials/tour/case-classes.md index bb732efd07..593985c0f9 100644 --- a/tutorials/tour/case-classes.md +++ b/tutorials/tour/case-classes.md @@ -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. + +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("john.doe@mail.com", "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(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!)" ``` 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. +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 = "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?") + +// 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. + From 2dfa9c46d85919df3951c3c86c6ff86463426230 Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Mon, 10 Oct 2016 22:31:47 +1000 Subject: [PATCH 2/7] Put tut:fail for code block that should fail to compile --- tutorials/tour/case-classes.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tutorials/tour/case-classes.md b/tutorials/tour/case-classes.md index 593985c0f9..de3047afa4 100644 --- a/tutorials/tour/case-classes.md +++ b/tutorials/tour/case-classes.md @@ -41,12 +41,15 @@ val title = emailFromJohn.title println(title) // prints "Greetings From John!" ``` -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: +With case classes, you cannot mutate their fields directly. -```tut +```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. +``` +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!)" From 6230bc9048458bac3ee6a9e05d23fc68b0b4c1ad Mon Sep 17 00:00:00 2001 From: jtcwang Date: Thu, 13 Oct 2016 22:12:51 +1000 Subject: [PATCH 3/7] Remove mentions of generated code + reworded last paragraph --- tutorials/tour/case-classes.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tutorials/tour/case-classes.md b/tutorials/tour/case-classes.md index de3047afa4..15538bbdd7 100644 --- a/tutorials/tour/case-classes.md +++ b/tutorials/tour/case-classes.md @@ -76,8 +76,7 @@ They are equal! SMS is: SMS(12345, Hello!) ``` -With Case Classes, all the necessary machinery required for using them in **Pattern Matching** is automatically generated by the compiler. -Here's a function that prints out different messages depending on what type of Notification is received: +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 = { @@ -131,7 +130,10 @@ val specialSms = SMS("55555", "I'm here! Where are you?") ``` -_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. +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 makes branching logic simpler, which means less bugs and more readable code. + From 87582d466395d38fd3850e417102cdf040e237ec Mon Sep 17 00:00:00 2001 From: jtcwang Date: Thu, 13 Oct 2016 22:13:40 +1000 Subject: [PATCH 4/7] remove mentions of generated code --- tutorials/tour/case-classes.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/tutorials/tour/case-classes.md b/tutorials/tour/case-classes.md index 15538bbdd7..3208fa2d1f 100644 --- a/tutorials/tour/case-classes.md +++ b/tutorials/tour/case-classes.md @@ -17,8 +17,6 @@ Scala supports the notion of _case classes_. Case classes are just regular class * 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. - 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 From 4c647e531b76bfdd924d9c383373a248380543f2 Mon Sep 17 00:00:00 2001 From: jtcwang Date: Thu, 13 Oct 2016 22:18:50 +1000 Subject: [PATCH 5/7] reword last bullet point --- tutorials/tour/case-classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/tour/case-classes.md b/tutorials/tour/case-classes.md index 3208fa2d1f..b71cde5b5b 100644 --- a/tutorials/tour/case-classes.md +++ b/tutorials/tour/case-classes.md @@ -132,6 +132,6 @@ When programming in Scala, it is recommended that you use Case Classes pervasive * 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 makes branching logic simpler, which means less bugs and more readable code. +* Pattern Matching simplifies branching logic, which leads to less bugs and more readable code. From bc8cf17cb1d52194577aa643035cf89ed54ac715 Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Fri, 14 Oct 2016 23:39:33 +1000 Subject: [PATCH 6/7] lower case "Pattern Matching" and "Case Classes" since they're not pronouns --- tutorials/tour/case-classes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tutorials/tour/case-classes.md b/tutorials/tour/case-classes.md index b71cde5b5b..e7d44245c7 100644 --- a/tutorials/tour/case-classes.md +++ b/tutorials/tour/case-classes.md @@ -13,7 +13,7 @@ tutorial-previous: currying 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) +* Decomposable through [pattern matching](pattern-matching.html) * Compared by structural equality instead of by reference * Succinct to instantiate and operate on @@ -26,7 +26,7 @@ case class SMS(sourceNumber: String, message: String) extends Notification case class VoiceRecording(contactName: String, link: String) extends Notification ``` -Instantiating a Case class is easy: (Note that we don't need to use the `new` keyword) +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!") @@ -74,7 +74,7 @@ They are equal! SMS is: SMS(12345, Hello!) ``` -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: +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 = { @@ -128,10 +128,10 @@ val specialSms = SMS("55555", "I'm here! Where are you?") ``` -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: +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. +* Pattern matching simplifies branching logic, which leads to less bugs and more readable code. From aee907bef0fb905c582ff3008d140c1772b3dce7 Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Fri, 14 Oct 2016 23:59:43 +1000 Subject: [PATCH 7/7] comments about using 'vars' in case class fields. --- tutorials/tour/case-classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/tour/case-classes.md b/tutorials/tour/case-classes.md index e7d44245c7..d58842e432 100644 --- a/tutorials/tour/case-classes.md +++ b/tutorials/tour/case-classes.md @@ -39,7 +39,7 @@ val title = emailFromJohn.title println(title) // prints "Greetings From John!" ``` -With case classes, you cannot mutate their fields directly. +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.