From ee20dda99fbbe3aacbb1a8fdea0a1366bd033c4a Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Thu, 6 Oct 2022 01:38:02 +0800 Subject: [PATCH 1/7] add code tabs in num9 --- _overviews/scala3-book/taste-modeling.md | 99 +++++++++++++++++++++--- 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/_overviews/scala3-book/taste-modeling.md b/_overviews/scala3-book/taste-modeling.md index 666fc0865d..7f0b9f6484 100644 --- a/_overviews/scala3-book/taste-modeling.md +++ b/_overviews/scala3-book/taste-modeling.md @@ -13,12 +13,9 @@ next-page: taste-methods NOTE: I kept the OOP section first, assuming that most readers will be coming from an OOP background. {% endcomment %} - Scala supports both functional programming (FP) and object-oriented programming (OOP), as well as a fusion of the two paradigms. This section provides a quick overview of data modeling in OOP and FP. - - ## OOP Domain Modeling When writing code in an OOP style, your two main tools for data encapsulation are _traits_ and _classes_. @@ -38,6 +35,26 @@ Later, when you want to create concrete implementations of attributes and behavi As an example of how to use traits as interfaces, here are three traits that define well-organized and modular behaviors for animals like dogs and cats: +{% tabs traits class=tabs-scala-version %} +{% tab 'Scala 2' for=traits %} +```scala +trait Speaker { + def speak(): String // has no body, so it’s abstract +} + +trait TailWagger { + def startTail(): Unit = println("tail is wagging") + def stopTail(): Unit = println("tail is stopped") +} + +trait Runner { + def startRunning(): Unit = println("I’m running") + def stopRunning(): Unit = println("Stopped running") +} +``` +{% endtab %} + +{% tab 'Scala 3' for=traits %} ```scala trait Speaker: def speak(): String // has no body, so it’s abstract @@ -50,27 +67,57 @@ trait Runner: def startRunning(): Unit = println("I’m running") def stopRunning(): Unit = println("Stopped running") ``` +{% endtab %} +{% endtabs %} Given those traits, here’s a `Dog` class that extends all of those traits while providing a behavior for the abstract `speak` method: +{% tabs traits-class class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-class %} +```scala +class Dog(name: String) extends Speaker, TailWagger, Runner { + def speak(): String = "Woof!" +} +``` +{% endtab %} + +{% tab 'Scala 3' for=traits-class %} ```scala class Dog(name: String) extends Speaker, TailWagger, Runner: def speak(): String = "Woof!" ``` +{% endtab %} +{% endtabs %} Notice how the class extends the traits with the `extends` keyword. Similarly, here’s a `Cat` class that implements those same traits while also overriding two of the concrete methods it inherits: +{% tabs traits-override class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-override %} +```scala +class Cat(name: String) extends Speaker, TailWagger, Runner { + def speak(): String = "Meow" + override def startRunning(): Unit = println("Yeah ... I don’t run") + override def stopRunning(): Unit = println("No need to stop") +} +``` +{% endtab %} + +{% tab 'Scala 3' for=traits-override %} ```scala class Cat(name: String) extends Speaker, TailWagger, Runner: def speak(): String = "Meow" override def startRunning(): Unit = println("Yeah ... I don’t run") override def stopRunning(): Unit = println("No need to stop") ``` +{% endtab %} +{% endtabs %} These examples show how those classes are used: +{% tabs traits-use class=tabs-scala-version %} +{% tab 'Scala 2 and 3' for=traits-use %} ```scala val d = Dog("Rover") println(d.speak()) // prints "Woof!" @@ -80,16 +127,32 @@ println(c.speak()) // "Meow" c.startRunning() // "Yeah ... I don’t run" c.stopRunning() // "No need to stop" ``` +{% endtab %} +{% endtabs %} If that code makes sense---great, you’re comfortable with traits as interfaces. If not, don’t worry, they’re explained in more detail in the [Domain Modeling][data-1] chapter. - ### Classes Scala _classes_ are used in OOP-style programming. Here’s an example of a class that models a “person.” In OOP fields are typically mutable, so `firstName` and `lastName` are both declared as `var` parameters: +{% tabs class_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=class_1 %} +```scala +class Person(var firstName: String, var lastName: String) { + def printFullName() = println(s"$firstName $lastName") +} + +val p = Person("John", "Stephens") +println(p.firstName) // "John" +p.lastName = "Legend" +p.printFullName() // "John Legend" +``` +{% endtab %} + +{% tab 'Scala 3' for=class_1 %} ```scala class Person(var firstName: String, var lastName: String): def printFullName() = println(s"$firstName $lastName") @@ -99,17 +162,22 @@ println(p.firstName) // "John" p.lastName = "Legend" p.printFullName() // "John Legend" ``` +{% endtab %} +{% endtabs %} Notice that the class declaration creates a constructor: +{% tabs class_2 class=tabs-scala-version %} +{% tab 'Scala 2 and 3' for=class_2 %} ```scala // this code uses that constructor val p = Person("John", "Stephens") ``` +{% endtab %} +{% endtabs %} Constructors and other class-related topics are covered in the [Domain Modeling][data-1] chapter. - ## FP Domain Modeling {% comment %} @@ -124,7 +192,6 @@ When writing code in an FP style, you’ll use these constructs: - Case classes - Traits - ### Enums The `enum` construct is a great way to model algebraic data types (ADTs) in Scala 3. @@ -136,6 +203,8 @@ For instance, a pizza has three main attributes: These are concisely modeled with enums: +{% tabs enum_1 class=tabs-scala-version %} +{% tab 'Scala 3 only' for=enum_1 %} ```scala enum CrustSize: case Small, Medium, Large @@ -146,9 +215,13 @@ enum CrustType: enum Topping: case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions ``` +{% endtab %} +{% endtabs %} Once you have an enum you can use it in all of the ways you normally use a trait, class, or object: +{% tabs enum_2 class=tabs-scala-version %} +{% tab 'Scala 3 only' for=enum_2 %} ```scala import CrustSize.* val currentCrustSize = Small @@ -162,18 +235,23 @@ currentCrustSize match // enums in an `if` statement if currentCrustSize == Small then println("Small crust size") ``` +{% endtab %} +{% endtabs %} Here’s another example of how to create and use an ADT with Scala: +{% tabs enum_3 class=tabs-scala-version %} +{% tab 'Scala 3 only' for=enum_3 %} ```scala enum Nat: case Zero case Succ(pred: Nat) ``` +{% endtab %} +{% endtabs %} Enums are covered in detail in the [Domain Modeling][data-1] section of this book, and in the [Reference documentation]({{ site.scala3ref }}/enums/enums.html). - ### Case classes The Scala `case` class lets you model concepts with immutable data structures. @@ -187,7 +265,6 @@ When the compiler sees the `case` keyword in front of a `class` it has these eff - `equals` and `hashCode` methods are generated to implement structural equality. - A default `toString` method is generated, which is helpful for debugging. - {% comment %} NOTE: Julien had a comment about how he decides when to use case classes vs classes. Add something here? {% endcomment %} @@ -196,6 +273,8 @@ You _can_ manually add all of those methods to a class yourself, but since those This code demonstrates several `case` class features: +{% tabs case-class class=tabs-scala-version %} +{% tab 'Scala 2 and 3' for=case-class %} ```scala // define a case class case class Person( @@ -218,9 +297,9 @@ p.name = "Joe" // error: can’t reassign a val field val p2 = p.copy(name = "Elton John") p2 // : Person = Person(Elton John,Singer) ``` +{% endtab %} +{% endtabs %} See the [Domain Modeling][data-1] sections for many more details on `case` classes. - - [data-1]: {% link _overviews/scala3-book/domain-modeling-tools.md %} From fecfca770ae7ed431acd0ef389ecebe46741df09 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sat, 8 Oct 2022 08:59:03 +0800 Subject: [PATCH 2/7] Update _overviews/scala3-book/taste-modeling.md Co-authored-by: Jamie Thompson --- _overviews/scala3-book/taste-modeling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_overviews/scala3-book/taste-modeling.md b/_overviews/scala3-book/taste-modeling.md index 7f0b9f6484..8fb3dab29b 100644 --- a/_overviews/scala3-book/taste-modeling.md +++ b/_overviews/scala3-book/taste-modeling.md @@ -75,7 +75,7 @@ Given those traits, here’s a `Dog` class that extends all of those traits whil {% tabs traits-class class=tabs-scala-version %} {% tab 'Scala 2' for=traits-class %} ```scala -class Dog(name: String) extends Speaker, TailWagger, Runner { +class Dog(name: String) extends Speaker with TailWagger with Runner { def speak(): String = "Woof!" } ``` From 0f31b08461ae43fbbb712613d25e5f2ff58fe9d2 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sat, 8 Oct 2022 08:59:10 +0800 Subject: [PATCH 3/7] Update _overviews/scala3-book/taste-modeling.md Co-authored-by: Jamie Thompson --- _overviews/scala3-book/taste-modeling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_overviews/scala3-book/taste-modeling.md b/_overviews/scala3-book/taste-modeling.md index 8fb3dab29b..9a7b509da3 100644 --- a/_overviews/scala3-book/taste-modeling.md +++ b/_overviews/scala3-book/taste-modeling.md @@ -96,7 +96,7 @@ Similarly, here’s a `Cat` class that implements those same traits while also o {% tabs traits-override class=tabs-scala-version %} {% tab 'Scala 2' for=traits-override %} ```scala -class Cat(name: String) extends Speaker, TailWagger, Runner { +class Cat(name: String) extends Speaker with TailWagger with Runner { def speak(): String = "Meow" override def startRunning(): Unit = println("Yeah ... I don’t run") override def stopRunning(): Unit = println("No need to stop") From d8a2d7ad37dfe3bbb66d6d40dd1e0e358ad3fb2d Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sat, 8 Oct 2022 08:59:17 +0800 Subject: [PATCH 4/7] Update _overviews/scala3-book/taste-modeling.md Co-authored-by: Jamie Thompson --- _overviews/scala3-book/taste-modeling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_overviews/scala3-book/taste-modeling.md b/_overviews/scala3-book/taste-modeling.md index 9a7b509da3..58eaed5f8a 100644 --- a/_overviews/scala3-book/taste-modeling.md +++ b/_overviews/scala3-book/taste-modeling.md @@ -145,7 +145,7 @@ class Person(var firstName: String, var lastName: String) { def printFullName() = println(s"$firstName $lastName") } -val p = Person("John", "Stephens") +val p = new Person("John", "Stephens") println(p.firstName) // "John" p.lastName = "Legend" p.printFullName() // "John Legend" From d2e2e36506803eea4a367bb622684e4004e6ca7d Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sat, 15 Oct 2022 01:39:26 +0800 Subject: [PATCH 5/7] add scala2 code. --- _overviews/scala3-book/taste-modeling.md | 67 +++++++++++++++++++++--- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/_overviews/scala3-book/taste-modeling.md b/_overviews/scala3-book/taste-modeling.md index 58eaed5f8a..7d8c0ac14e 100644 --- a/_overviews/scala3-book/taste-modeling.md +++ b/_overviews/scala3-book/taste-modeling.md @@ -37,6 +37,7 @@ As an example of how to use traits as interfaces, here are three traits that def {% tabs traits class=tabs-scala-version %} {% tab 'Scala 2' for=traits %} + ```scala trait Speaker { def speak(): String // has no body, so it’s abstract @@ -52,9 +53,11 @@ trait Runner { def stopRunning(): Unit = println("Stopped running") } ``` + {% endtab %} {% tab 'Scala 3' for=traits %} + ```scala trait Speaker: def speak(): String // has no body, so it’s abstract @@ -67,6 +70,7 @@ trait Runner: def startRunning(): Unit = println("I’m running") def stopRunning(): Unit = println("Stopped running") ``` + {% endtab %} {% endtabs %} @@ -74,18 +78,22 @@ Given those traits, here’s a `Dog` class that extends all of those traits whil {% tabs traits-class class=tabs-scala-version %} {% tab 'Scala 2' for=traits-class %} + ```scala class Dog(name: String) extends Speaker with TailWagger with Runner { def speak(): String = "Woof!" } ``` + {% endtab %} {% tab 'Scala 3' for=traits-class %} + ```scala class Dog(name: String) extends Speaker, TailWagger, Runner: def speak(): String = "Woof!" ``` + {% endtab %} {% endtabs %} @@ -95,6 +103,7 @@ Similarly, here’s a `Cat` class that implements those same traits while also o {% tabs traits-override class=tabs-scala-version %} {% tab 'Scala 2' for=traits-override %} + ```scala class Cat(name: String) extends Speaker with TailWagger with Runner { def speak(): String = "Meow" @@ -102,22 +111,40 @@ class Cat(name: String) extends Speaker with TailWagger with Runner { override def stopRunning(): Unit = println("No need to stop") } ``` + {% endtab %} {% tab 'Scala 3' for=traits-override %} + ```scala class Cat(name: String) extends Speaker, TailWagger, Runner: def speak(): String = "Meow" override def startRunning(): Unit = println("Yeah ... I don’t run") override def stopRunning(): Unit = println("No need to stop") ``` + {% endtab %} {% endtabs %} These examples show how those classes are used: {% tabs traits-use class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=traits-use %} +{% tab 'Scala 2' for=traits-use %} + +```scala +val d = new Dog("Rover") +println(d.speak()) // prints "Woof!" + +val c = new Cat("Morris") +println(c.speak()) // "Meow" +c.startRunning() // "Yeah ... I don’t run" +c.stopRunning() // "No need to stop" +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits-use %} + ```scala val d = Dog("Rover") println(d.speak()) // prints "Woof!" @@ -127,6 +154,7 @@ println(c.speak()) // "Meow" c.startRunning() // "Yeah ... I don’t run" c.stopRunning() // "No need to stop" ``` + {% endtab %} {% endtabs %} @@ -140,6 +168,7 @@ Here’s an example of a class that models a “person.” In OOP fields are typ {% tabs class_1 class=tabs-scala-version %} {% tab 'Scala 2' for=class_1 %} + ```scala class Person(var firstName: String, var lastName: String) { def printFullName() = println(s"$firstName $lastName") @@ -150,9 +179,11 @@ println(p.firstName) // "John" p.lastName = "Legend" p.printFullName() // "John Legend" ``` + {% endtab %} {% tab 'Scala 3' for=class_1 %} + ```scala class Person(var firstName: String, var lastName: String): def printFullName() = println(s"$firstName $lastName") @@ -162,17 +193,29 @@ println(p.firstName) // "John" p.lastName = "Legend" p.printFullName() // "John Legend" ``` + {% endtab %} {% endtabs %} Notice that the class declaration creates a constructor: {% tabs class_2 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=class_2 %} +{% tab 'Scala 2' for=class_2 %} + +```scala +// this code uses that constructor +val p = new Person("John", "Stephens") +``` + +{% endtab %} + +{% tab 'Scala 3' for=class_2 %} + ```scala // this code uses that constructor val p = Person("John", "Stephens") ``` + {% endtab %} {% endtabs %} @@ -203,8 +246,9 @@ For instance, a pizza has three main attributes: These are concisely modeled with enums: -{% tabs enum_1 class=tabs-scala-version %} -{% tab 'Scala 3 only' for=enum_1 %} +{% tabs enum_1 %} +{% tab 'Scala 3 Only' for=enum_1 %} + ```scala enum CrustSize: case Small, Medium, Large @@ -215,13 +259,15 @@ enum CrustType: enum Topping: case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions ``` + {% endtab %} {% endtabs %} Once you have an enum you can use it in all of the ways you normally use a trait, class, or object: -{% tabs enum_2 class=tabs-scala-version %} -{% tab 'Scala 3 only' for=enum_2 %} +{% tabs enum_2 %} +{% tab 'Scala 3 Only' for=enum_2 %} + ```scala import CrustSize.* val currentCrustSize = Small @@ -235,18 +281,21 @@ currentCrustSize match // enums in an `if` statement if currentCrustSize == Small then println("Small crust size") ``` + {% endtab %} {% endtabs %} Here’s another example of how to create and use an ADT with Scala: -{% tabs enum_3 class=tabs-scala-version %} -{% tab 'Scala 3 only' for=enum_3 %} +{% tabs enum_3 %} +{% tab 'Scala 3 Only' for=enum_3 %} + ```scala enum Nat: case Zero case Succ(pred: Nat) ``` + {% endtab %} {% endtabs %} @@ -275,6 +324,7 @@ This code demonstrates several `case` class features: {% tabs case-class class=tabs-scala-version %} {% tab 'Scala 2 and 3' for=case-class %} + ```scala // define a case class case class Person( @@ -297,6 +347,7 @@ p.name = "Joe" // error: can’t reassign a val field val p2 = p.copy(name = "Elton John") p2 // : Person = Person(Elton John,Singer) ``` + {% endtab %} {% endtabs %} From b5fca80d16b414e5f07c18c206629e80528533e4 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sat, 15 Oct 2022 01:55:11 +0800 Subject: [PATCH 6/7] correct tab style. --- _overviews/scala3-book/taste-modeling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_overviews/scala3-book/taste-modeling.md b/_overviews/scala3-book/taste-modeling.md index 7d8c0ac14e..40e089927f 100644 --- a/_overviews/scala3-book/taste-modeling.md +++ b/_overviews/scala3-book/taste-modeling.md @@ -322,7 +322,7 @@ You _can_ manually add all of those methods to a class yourself, but since those This code demonstrates several `case` class features: -{% tabs case-class class=tabs-scala-version %} +{% tabs case-class %} {% tab 'Scala 2 and 3' for=case-class %} ```scala From 85e2caeea6844456ccabd42e836d892189cd1745 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 18 Oct 2022 17:02:25 +0200 Subject: [PATCH 7/7] adjust formatting --- _overviews/scala3-book/taste-modeling.md | 102 +++++++++++++++++++---- 1 file changed, 84 insertions(+), 18 deletions(-) diff --git a/_overviews/scala3-book/taste-modeling.md b/_overviews/scala3-book/taste-modeling.md index 40e089927f..f28ceb550e 100644 --- a/_overviews/scala3-book/taste-modeling.md +++ b/_overviews/scala3-book/taste-modeling.md @@ -229,25 +229,59 @@ I didn’t include that because I didn’t know if enums are intended to replace the Scala2 “sealed trait + case class” pattern. How to resolve? {% endcomment %} -When writing code in an FP style, you’ll use these constructs: +When writing code in an FP style, you’ll use these concepts: -- Enums to define ADTs -- Case classes -- Traits +- Algebraic Data Types to define the data +- Traits for functionality on the data. -### Enums +### Enumerations and Sum Types + +Sum types are one way to model algebraic data types (ADTs) in Scala. + +They are used when data can be represented with different choices. -The `enum` construct is a great way to model algebraic data types (ADTs) in Scala 3. For instance, a pizza has three main attributes: - Crust size - Crust type - Toppings -These are concisely modeled with enums: +These are concisely modeled with enumerations, which are sum types that only contain singleton values: + +{% tabs enum_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_1 %} + +In Scala 2 `sealed` classes and `case object` are combined to define an enumeration: + +```scala +sealed abstract class CrustSize +object CrustSize { + case object Small extends CrustSize + case object Medium extends CrustSize + case object Large extends CrustSize +} + +sealed abstract class CrustType +object CrustType { + case object Thin extends CrustType + case object Thick extends CrustType + case object Regular extends CrustType +} + +sealed abstract class Topping +object Topping { + case object Cheese extends Topping + case object Pepperoni extends Topping + case object BlackOlives extends Topping + case object GreenOlives extends Topping + case object Onions extends Topping +} +``` + +{% endtab %} +{% tab 'Scala 3' for=enum_1 %} -{% tabs enum_1 %} -{% tab 'Scala 3 Only' for=enum_1 %} +Scala 3 offers the `enum` construct for defining enumerations: ```scala enum CrustSize: @@ -263,10 +297,28 @@ enum Topping: {% endtab %} {% endtabs %} -Once you have an enum you can use it in all of the ways you normally use a trait, class, or object: +Once you have an enumeration you can import its members as ordinary values: + +{% tabs enum_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_2 %} + +```scala +import CrustSize._ +val currentCrustSize = Small + +// enums in a `match` expression +currentCrustSize match { + case Small => println("Small crust size") + case Medium => println("Medium crust size") + case Large => println("Large crust size") +} + +// enums in an `if` statement +if (currentCrustSize == Small) println("Small crust size") +``` -{% tabs enum_2 %} -{% tab 'Scala 3 Only' for=enum_2 %} +{% endtab %} +{% tab 'Scala 3' for=enum_2 %} ```scala import CrustSize.* @@ -285,10 +337,23 @@ if currentCrustSize == Small then println("Small crust size") {% endtab %} {% endtabs %} -Here’s another example of how to create and use an ADT with Scala: +Here’s another example of how to create a sum type with Scala, this would not be called an enumeration because the `Succ` case has parameters: -{% tabs enum_3 %} -{% tab 'Scala 3 Only' for=enum_3 %} +{% tabs enum_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_3 %} + +```scala +sealed abstract class Nat +object Nat { + case object Zero extends Nat + case class Succ(pred: Nat) extends Nat +} +``` + +Sum Types are covered in detail in the [Domain Modeling]({% link _overviews/scala3-book/domain-modeling-tools.md %}) section of this book. + +{% endtab %} +{% tab 'Scala 3' for=enum_3 %} ```scala enum Nat: @@ -296,14 +361,15 @@ enum Nat: case Succ(pred: Nat) ``` +Enums are covered in detail in the [Domain Modeling]({% link _overviews/scala3-book/domain-modeling-tools.md %}) section of this book, and in the [Reference documentation]({{ site.scala3ref }}/enums/enums.html). + {% endtab %} {% endtabs %} -Enums are covered in detail in the [Domain Modeling][data-1] section of this book, and in the [Reference documentation]({{ site.scala3ref }}/enums/enums.html). +### Product Types -### Case classes +A product type is an algebraic data type (ADT) that only has one shape, for example a singleton object, represented in Scala by a `case` object; or an immutable structure with accessible fields, represented by a `case` class. -The Scala `case` class lets you model concepts with immutable data structures. A `case` class has all of the functionality of a `class`, and also has additional features baked in that make them useful for functional programming. When the compiler sees the `case` keyword in front of a `class` it has these effects and benefits: