Skip to content

add code tabs in num9 #2581

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 8 commits into from
Oct 18, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 216 additions & 20 deletions _overviews/scala3-book/taste-modeling.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_.
Expand All @@ -38,6 +35,29 @@ 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
Expand All @@ -51,26 +71,80 @@ trait Runner:
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 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 %}

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 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")
}
```

{% 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' 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!"
Expand All @@ -81,15 +155,35 @@ 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 = new 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")
Expand All @@ -100,15 +194,32 @@ 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' 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")
```

Constructors and other class-related topics are covered in the [Domain Modeling][data-1] chapter.
{% endtab %}
{% endtabs %}

Constructors and other class-related topics are covered in the [Domain Modeling][data-1] chapter.

## FP Domain Modeling

Expand All @@ -118,23 +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.

### Enumerations and Sum Types

### Enums
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 %}

Scala 3 offers the `enum` construct for defining enumerations:

```scala
enum CrustSize:
Expand All @@ -147,7 +294,31 @@ enum Topping:
case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions
```

Once you have an enum you can use it in all of the ways you normally use a trait, class, or object:
{% endtab %}
{% endtabs %}

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")
```

{% endtab %}
{% tab 'Scala 3' for=enum_2 %}

```scala
import CrustSize.*
Expand All @@ -163,20 +334,42 @@ currentCrustSize match
if currentCrustSize == Small then println("Small crust size")
```

Here’s another example of how to create and use an ADT with Scala:
{% endtab %}
{% endtabs %}

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 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:
case Zero
case Succ(pred: Nat)
```

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).
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 %}

### Case classes
### Product Types

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:

Expand All @@ -187,7 +380,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 %}
Expand All @@ -196,6 +388,9 @@ 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 %}
{% tab 'Scala 2 and 3' for=case-class %}

```scala
// define a case class
case class Person(
Expand All @@ -219,8 +414,9 @@ val p2 = p.copy(name = "Elton John")
p2 // : Person = Person(Elton John,Singer)
```

See the [Domain Modeling][data-1] sections for many more details on `case` classes.

{% 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 %}